[pentair] Many enhancements since original commit, including (#13485)

* Updated per design review comments
* Added unitHint to Dimensionless items

Signed-off-by: Jeff James <jeff@james-online.com>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
jsjames 2024-07-08 14:58:35 -07:00 committed by Ciprian Pascu
parent e1d0204751
commit 663c111e1e
61 changed files with 8920 additions and 2169 deletions

View File

@ -1,8 +1,10 @@
# Pentair Pool
This is an openHAB binding for a Pentair Pool System.
It is based on combined efforts of many on the internet in reverse-engineering the proprietary Pentair protocol (see References section).
The binding was developed and tested on a system with a Pentair EasyTouch controller, but should operate with other Pentair systems.
It is based on combined efforts of many on the Internet in reverse-engineering the proprietary Pentair protocol (see References section).
The binding was developed and tested on a system with a Pentair EasyTouch controller, but will also operate with the Pentair IntelliTouch/SunTouch controllers.
Note that with the Pentair IntelliCenter controllers, the functionality will be limited since that utilizes a different protocol which has not yet been implemented.
Further, if there is interest to improve functionality or address issues with any of the Pentair controllers, please reach out to the binding author.
## Hardware Setup
@ -17,24 +19,26 @@ I have cited several of those in the References section below.
### Connecting adapter to your system
A USB or serial RS-485 interface or IP based interface can be used to interface to the Pentair system bus.
The binding includes 2 different bridge Things depending on which type of interface you use, serial_bridge or ip_bridge.
The binding includes 2 different Bridges depending on which type of interface you use, serial_bridge or ip_bridge.
If your openHAB system is physically located far from your Pentair equipment or indoor control panel, you can use a Raspberry Pi or other computer to redirect USB/serial port traffic over the internet using a program called ser2sock (see Reference section).
An example setup would run the following command: "ser2sock -p 10000 -s /dev/ttyUSB1 -b 9600 -d".
Note: This is the setup utlized for the majority of my testing of this binding.
Note: If you are on a Linux system, the framework may not see a symbolically linked device (i.e. /dev/ttyRS485).
To use a symbolically linked device, add the following line to _/etc/default/openhab_ `EXTRA_JAVA_OPTS="-Dgnu.io.rxtx.SerialPorts=/dev/ttyRS485"`
Note: This is the setup utilized for the majority of my testing of this binding.
Once you have the interface connected to your system, it is best to test basic connectivity.
Note the protocol is a binary protocol (not ASCII text based) and in order to view the communication packets, one must use a program capable of a binary/HEX mode.
If connected properly, you will see a periodic traffic with packets staring with FF00FFA5.
This is the preamble for Pentairs communication packet.
This is the preamble for Pentair's communication packet.
After you see this traffic, you can proceed to configuring the Pentair binding in openHAB.
Note: Many adapters use A and B to represent Data+ and Data-. There is no reliable standard for determining which is Data+ and Data-. If you connect the system in reverse, you will still see serial data, however it will be corrupted. Look at your data coming from your device and look for a repeated "FFa5". If you don't see that preamble reliably, you may try switching your data lines."
#### USB/Serial interface
For a USB/Serial interface, you can use most terminal emulators. For Linux, you can use minicom with the following options: `minicom -H -D /dev/ttyUSB1 -b 9600`
For a USB/Serial interface, you can use most terminal emulators.
For Linux, you can use minicom with the following options: `minicom -H -D /dev/ttyUSB1 -b 9600`
#### IP interface
@ -42,27 +46,30 @@ For an IP based interface (or utilizing ser2sock) on a Linux system, you can use
### Pentair Controller panel configuration
In order for the Pentair EasyTouch controller to receive commands from this binding, you may need to enable "Spa-side" remote on the controller itself.
In order for the Pentair controller to receive commands from this binding, you may need to enable "Spa-side" remote on the controller itself.
## Supported Things
This binding supports the following thing types:
| Thing | Thing Type | Description |
| ThingType UID | Thing Type | Description |
| --------------- | :--------: | --------------------------------------- |
| ip_bridge | Bridge | A TCP network RS-485 bridge device. |
| serial_bridge | Bridge | A USB or serial RS-485 device. |
| EasyTouch | Thing | Pentiar EasyTouch pool controller. |
| Intelliflo Pump | Thing | Pentair Intelliflo variable speed pump. |
| Intellichlor | Thing | Pentair Intellichlor chlorinator. |
| controller | Thing | Pentair EasyTouch, SunTouch, or IntelliTouch pool controller. |
| intelliflo | Thing | Pentair IntelliFlo variable speed pump. |
| intellichlor | Thing | Pentair IntelliChlor chlorinator. |
| intellichem | Thing | Pentair IntelliChem.
## Binding Configuration
There are no overall binding configurations that need to be set up as all configuration is done at the "Thing" level.
There are no overall binding configurations that need to be set up as all configuration is done at the "Bridge/Thing" level.
## Thing Configuration
## Bridge Configuration
The following table shows the available configuration parameters for each thing.
A Bridge item must first be configured to gain access to the Pentair bus.
This can be done via the interactive setup pages in openHAB or manually through a .thing configuration file.
The following table shows the parameters for each Bridge.
| Thing | Configuration Parameters |
| ------------- | ------------------------------------------------------------ |
@ -72,120 +79,200 @@ The following table shows the available configuration parameters for each thing.
| serial_bridge | serialPort - Serial port for the IT-100s bridge - Required. |
| | baud - Baud rate of the IT-100 bridge - Not Required - default = 9600. |
| | pollPeriod - Period of time in minutes between the poll command being sent to the IT-100 bridge - Not Required - default=1. |
| | id - ID to use when communciating on Pentair control bus - default = 34. |
Currently automatic discovery is not supported.
Here is an example of a thing configuration file called 'pentair.things':
| | id - ID to use when communicating on Pentair control bus - default = 34. |
```java
Bridge pentair:ip_bridge:1 [ address="192.168.1.202", port=10001 ] {
easytouch main [ id=16 ]
controller main [ id=16 ]
intelliflo pump1 [ id=96 ]
intellichlor ic40
intellichem chem
}
```
For a serial bridge you would use a configuration similar to this, again saved as 'pentair.things':
```java
Bridge pentair:serial_bridge:1 [ serialPort="/dev/ttyUSB0" ] {
easytouch main [ id=16 ]
controller main [ id=16 ]
intelliflo pump1 [ id=96 ]
intellichlor ic40
intellichem chem
}
```
## Channels
## Things & Channels
Pentair things support a variety of channels as seen below in the following table:
### Thing: Controller
| Channel | Item Type | Description |
| -------------------- | --------- | ------------------------------------------------------------ |
| EasyTouch Controller | | |
| pooltemp | Number | Current pool temperature (readonly) |
| spatemp | Number | Current spa temperature (readonly) |
| airtemp | Number | Current air temperature (readonly) |
| solartemp | Number | Current solar temperature (readonly) |
| poolheatmode | Number | Current heat mode setting for pool (readonly): 0=Off, 1=Heater, 2=Solar Preferred, 3=Solar |
| poolheatmodestr | String | Current heat mode setting for pool in string form (readonly) |
| spaheatmode | Number | Current heat mode setting for spa (readonly): 0=Off, 1=Heater, 2=Solar Preferred, 3=Solar |
| spaheatmodestr | String | Current heat mode setting for spa in string form (readonly)> |
| poolsetpoint | Number | Current pool temperature set point |
| spasetpoint | Number | Current spa temperature set point |
| heatactive | Number | Heater mode is active |
| pool | Switch | Primary pool mode |
| spa | Switch | Spa mode |
| aux1 | Switch | Aux1 mode |
| aux2 | Switch | Aux2 mode |
| aux3 | Switch | Aux3 mode |
| aux4 | Switch | Aux4 mode |
| aux5 | Switch | Aux5 mode |
| aux6 | Switch | Aux6 mode |
| aux7 | Switch | Aux7 mode |
| feature1 | Switch | Feature1 mode |
| feature2 | Switch | Feature2 mode |
| feature3 | Switch | Feature3 mode |
| feature4 | Switch | Feature4 mode |
| feature5 | Switch | Feature5 mode |
| feature6 | Switch | Feature6 mode |
| feature7 | Switch | Feature7 mode |
| feature8 | Switch | Feature8 mode |
| IntelliChlor | | |
| saltoutput | Number | Current salt output % (readonly) |
| salinity | Number | Salinity (ppm) (readonly) |
| IntelliFlo Pump | | |
| run | Number | Pump running (readonly) |
| drivestate | Number | Pump drivestate (readonly) |
| mode | Number | Pump mode (readonly) |
| rpm | Number | Pump RPM (readonly) |
| power | Number | Pump power in Watts (readonly) |
| error | Number | Pump error (readonly) |
| ppc | Number | Pump PPC? (readonly) |
Represents and interfaces with a Pentair pool controller in the system. This binding should work for both Intellitouch and EasyTouch systems.
Feature availability is dependent on the version of hardware and firmware versions of your specific controller.
## Full Example
#### Synchronize Time
The following is an example of an item file (pentair.items), you can change the °F to °C if you are using metric temperature units:
This configuration setting will instruct the binding to automatically update the controller's clock every 24 hours with the value from the openHAB server.
This is useful to keep the pool system clock set correct and automatically adjust for daylight savings time.
| Channel Group | Channel | Type | | Description |
| :------------------------------: | :-------: | :----: | :-: | :--------------: |
| pool, spa, aux[1-8], feature[1-8] | switch | Switch | RW | Indicates the particulcar circuit or feature is on or off. |
| " | name | String | R | Name of circuit |
| " | feature | String | R | Feature of ciruit |
| poolheat, spaheat | setpoint | Number:Temperature | RW | Temperature setpoint |
| " | temperature | Number:Temperature | R | Current water temperature. Note, the temperature is only valid while in either pool or spa mode. |
| " | heatmode | String | R | Heat mode configured. Values: NONE, HEATER, SOLARPREFERRED, SOLAR |
| schedule[1-9] | schedule | String | RW | Summary string of schedule. |
| " | type | String | RW | Type of schedule. Note, to actually write the program to the controller, this channel must be written to with the same value 2 times within 5s. Values: NONE, NORMAL, EGGTIMER, ONCE ONLY |
| " | start | Number:Time | RW | Time of day to start schedule expressed in minutes. |
| " | end | Number:Time | RW | Time of day to end schedule expressed in minutes. In the case of EGG TIMER, this shoud be the duration. |
| " | circuit | Number | RW | Circuit/Feature the schedule will control. |
| " | days | String | RW | The days the schedule will run. S=Sunday, M=Monday, T=Tuesday, W=Wednesday, R=Thursday, F=Friday, Y=Saturday |
| status | lightmode | String | RW | Light mode. Values: OFF, ON, COLORSYNC, COLORSWIM, COLORSET, PARTY, ROMANCE, CARIBBEAN, AMERICAN, SUNSET, ROYAL, BLUE, GREEN, RED, WHITE, MAGENTA |
| " | solartemperature | Number:Temperature | R | Solar temperature sensor reading. |
| " | airtemperature | Number:Temperature | R | Air temperature sensor reading. |
| " | servicemode | Switch | R | Indicates whether controller is in service mode. |
| " | solaron | Switch | R | Indicates whether solar heat is on. |
| " | heateron | Switch | R | Indicates whether heater is on. |
#### Working with schedules
This binding allows both reading and writing of schedules and supports up to 9 schedules.
Programming of a schedule can be accomplished either by using the discrete channels linked to items (i.e. type, start, end, circuit, days) or you can concatenate those and use the `schedule` channel saved as a comma delimited string.
To prevent erroneous writes to the schedules though, one must write to the `type` channel the same value twice within 5 sec.
### Thing: IntelliChlor
Represents an Intellichlor module connected in your system. Currently, the values here are readonly.
| Channel | Type | | Description |
| :------------------: | :----: | :-: | :---------- |
| saltOutput | Number:Dimensionless | R | Current salt output %. |
| salinity | Number:Dimensionless | R | Salinity (ppm). |
| ok | Switch | R | System is operating normally. |
| lowFlow | Switch | R | Water flow rate is low. |
| lowSalt | Switch | R | Low salt level. |
| veryLowSalt | Switch | R | Very low salt level. |
| highCurrent | Switch | R | High current level. |
| cleanCell | Switch | R | Clean cell. |
| lowVoltage | Switch | R | Low voltage. |
| lowWaterTemp | Switch | R | Water temperature is too low for chlorine generation. |
| commError | Switch | R | Communication error. |
### Thing: IntelliFlo
Represents and interfaces to an Intelliflo pump.
When a controller is active in the system all pump values are read only since the pump can only have one master at a time.
If no controller is present or the controller is in service mode, the pump can be controlled directly from OpenHab.
| Channel | Type | | Description |
| :------------------: | :----: | :-: | :---------- |
| run | Switch | RW | Indicates whether the pump is running. |
| rpm | Number | RW | Pump RPM |
| gpm | Number:VolumetricFlowRate | R | Pump GPM (only valid for VF pumps) |
| power | Number:Power | R | Pump power (Watt) |
| status1 | Number | R | Pump status1. (not reversed engineered) |
| status2 | Number | R | Pump status2. (not reversed engineered) |
| runProgram | Number | RW | Run program (0 to stop, # to run) |
### Thing: IntelliChem
Represents and interfaces to an IntelliChem unit.
This is for monitoring of values only and IntelliChem cannot be directly controlled through this binding.
Note: This has limited testing since I don't own an IntelliChem
| Channel | Type | | Description |
| :------------------: | :----: | :-: | :---------- |
| phReading | Number | R | Current PH reading. |
| orpReading | Number | R | Current Oxidation Reduction Potential (ORP) reading. |
| phSetPoint | Number | R | Current PH set point. |
| orpSetPoint | Number | R | Oxidation Reduction Potential (ORP) set point. |
| tank1Level | Number | R | Tank 1 level (1-7). |
| tank2Level | Number | R | Tank 2 level (1-7). |
| calciumHardness | Number:Dimensionless | R | Calcium hardness PPM (mg/L). |
| cyaReading | Number | R | Cyanuric acid reading. |
| alkalinity | Number | R | Alkalinity reading. |
| phDoserType | String | R | The doser type for PH (None, CO2, Acid). |
| orpDOserType | String | R | The doser type for ORP (None, ORP). |
| phDoserStatus | Switch | R | Whether the chemical is currently dosing. |
| orpDoserStatus | Switch | R | Whether the chemical is currently dosing. |
| phDoseTime | Number:Time | R | The time a particular chemical has been dosing. |
| orpdoseTime | Number:Time | R | The time a particular chemical has been dosing. |
| lsi | Number | R | Langelier Saturation Index. |
| saltLevel | Number:Dimensionless | R | Current salt content reading of the water (PPM). |
| temperature | Number:Temperature | R | Current temperature. |
| alarmWaterFlow | Switch | R | Water flow alarm (on = no water flow). |
| alarmPh | Switch | R | PH alarm reported. |
| alarmOrp | Switch | R | ORP alarm reported. |
| alarmPhTank | Switch | R | PH tank alarm reported. |
| alarmOrpTank | Switch | R | ORP tank alarm reported. |
| alarmProbeFault | Switch | R | Probe fault alarm reported. |
| warningPhLockout | Switch | R | Unit is in PH Lockout. |
| warningPhDailyLimitReached | Switch | R | Daily limit of PH dosing has been reached. |
| warningOrpDailLimitReached | Switch | R | Daily limit of ORP dosing has been reached. |
| warningInvalidSetup | Switch | R | Invalid setup for the unit. |
| warningChlorinatorCommError | Switch | R | Error in communicating with the Chlorinator. |
## Example setup
### pentair.items
```java
Group gPool "Pool"
Group gPool (All)
Number Pool_Temp "Pool Temp [%.1f °F]" <temperature> (gPool) { channel = "pentair:easytouch:1:main:pooltemp" }
Number Spa_Temp "Spa Temp [%.1f °F]" <temperature> (gPool) { channel = "pentair:easytouch:1:main:spatemp" }
Number Air_Temp "Air Temp [%.1f °F]" <temperature> (gPool) { channel = "pentair:easytouch:1:main:airtemp" }
Number Solar_Temp "Solar Temp [%.1f °F]" <temperature> (gPool) { channel = "pentair:easytouch:1:main:solartemp" }
Number:Temperature Pool_Temp "Pool Temperature" <temperature> (gPool) { channel = "pentair:controller:1:main:poolheat#temperature" }
Number:Temperature Spa_Temp "Spa Temperature " <temperature> (gPool) { channel = "pentair:controller:1:main:spaheat#temperature" }
Number:Temperature Air_Temp "Air Temperature" <temperature> (gPool) { channel = "pentair:controller:1:main:status#airtemperature" }
Number:Temperature Solar_Temp "Solar Temperature" <temperature> (gPool) { channel = "pentair:controller:1:main:status#solartemperature" }
Number PoolHeatMode "Pool Heat Mode [%d]" (gPool) { channel="pentair:easytouch:1:main:poolheatmode" }
String PoolHeatModeStr "Pool Heat Mode [%s]" (gPool) { channel="pentair:easytouch:1:main:poolheatmodestr" }
Number SpaHeatMode "Spa Heat Mode [%d]" (gPool) { channel="pentair:easytouch:1:main:spaheatmode" }
String SpaHeatModeStr "Spa Heat Mode [%s]" (gPool) { channel="pentair:easytouch:1:main:spaheatmodestr" }
Number PoolSetPoint "Pool Set Point [%.1f °F]" <temperature> (gPool) { channel="pentair:easytouch:1:main:poolsetpoint" }
Number SpaSetPoint "Spa Set Point [%.1f °F]" <temperature> (gPool) { channel="pentair:easytouch:1:main:spasetpoint" }
Number HeatActive "Heat Active [%d]" (gPool) { channel="pentair:easytouch:1:main:heatactive" }
String PoolHeatMode "Pool Heat Mode [%s]" (gPool) { channel="pentair:controller:1:main:poolheat#heatmode" }
String SpaHeatMode "Spa Heat Mode [%s]" (gPool) { channel="pentair:controller:1:main:spaheat#heatmode" }
Number:Temperature PoolSetPoint "Pool Set Point" (gPool) { channel="pentair:controller:1:main:poolheat#setpoint" }
Number:Temperature SpaSetPoint "Spa Set Point" (gPool) { channel="pentair:controller:1:main:spaheat#setpoint" }
Switch Mode_Spa "Spa Mode" (gPool) { channel = "pentair:easytouch:1:main:spa" }
Switch Mode_Pool "Pool Mode" (gPool) { channel = "pentair:easytouch:1:main:pool" }
Switch Mode_PoolLight "Pool Light" (gPool) { channel = "pentair:easytouch:1:main:aux1" }
Switch Mode_SpaLight "Spa Light" (gPool) { channel = "pentair:easytouch:1:main:aux2" }
Switch Mode_Jets "Jets" (gPool) { channel = "pentair:easytouch:1:main:aux3" }
Switch Mode_Boost "Boost Mode" (gPool) { channel = "pentair:easytouch:1:main:aux4" }
Switch Mode_Aux5 "Aux5 Mode" (gPool) { channel = "pentair:easytouch:1:main:aux5" }
Switch Mode_Aux6 "Aux6 Mode" (gPool) { channel = "pentair:easytouch:1:main:aux6" }
Switch Mode_Aux7 "Aux7 Mode" (gPool) { channel = "pentair:easytouch:1:main:aux7" }
Switch Mode_Spillway "Spillway Mode" (gPool) { channel = "pentair:easytouch:1:main:feature1" }
String PoolLightMode "Light Mode" (gPool) { channel="pentair:controller:1:main:status#lightmode" }
Number SaltOutput "Salt Output [%d%%]" (gPool) { channel = "pentair:intellichlor:1:ic40:saltoutput" }
Number PoolHeatEnable "Pool Heat Enable [%d]" (gPool) { channel="pentair:controller:1:main:poolheatenable" }
Number SpaHeatEnable "Spa Heat Enable [%d]" (gPool) { channel="pentair:controller:1:main:spaheatenable" }
Switch Mode_Pool "Pool Mode" (gPool) { channel = "pentair:controller:1:main:pool#switch" }
Switch Mode_Spa "Spa" (gPool) { channel = "pentair:controller:1:main:spa#switch" }
Switch Mode_PoolLight "Pool Light" (gPool) { channel = "pentair:controller:1:main:aux1#switch" }
Switch Mode_SpaLight "Spa Light" (gPool) { channel = "pentair:controller:1:main:aux2#switch" }
Switch Mode_Jets "Jets" (gPool) { channel = "pentair:controller:1:main:aux3#switch" }
Switch Mode_Boost "Boost Mode" (gPool) { channel = "pentair:controller:1:main:aux4#switch" }
Switch Mode_Aux5 "Aux5 Mode" (gPool) { channel = "pentair:controller:1:main:aux5#switch" }
Switch Mode_Aux6 "Aux6 Mode" (gPool) { channel = "pentair:controller:1:main:aux6#switch" }
Switch Mode_Aux7 "Aux7 Mode" (gPool) { channel = "pentair:controller:1:main:aux7#switch" }
String ModeName_Pool "Pool Name [%s]" (gPool) { channel = "pentair:controller:1:main:pool#name" }
String ModeFunc_Pool "Pool Func [%s]" (gPool) { channel = "pentair:controller:1:main:pool#function" }
String ModeName_Spa "Spa Name [%s]" (gPool) { channel = "pentair:controller:1:main:spa#name" }
String ModeFunc_Spa "Spa Func [%s]" (gPool) { channel = "pentair:controller:1:main:spa#function" }
Switch Delay "Heater Delay" (gPool) { channel = "pentair:controller:1:main:status#heaterdelay" }
Number Salt_Output "Salt Output [%d%%]" (gPool) { channel = "pentair:intellichlor:1:ic40:salt_output" }
Number Salinity "Salinity [%d ppm]" (gPool) { channel = "pentair:intellichlor:1:ic40:salinity" }
Switch Pump_Run "Pump running [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:run" }
Number Pump_DriveState "Pump drivestate [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:drivestate" }
Number Pump_Mode "Pump Mode [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:mode" }
Switch Pump_Run "Pump run" (gPool) { channel = "pentair:intelliflo:1:pump1:run" }
Number Pump_RPM "Pump RPM [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:rpm" }
Number Pump_GPM "Pump GPM [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:gpm" }
Number Pump_Power "Pump Power [%d W]" (gPool) { channel = "pentair:intelliflo:1:pump1:power" }
Number Pump_Error "Pump Error [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:error" }
Number Pump_PPC "Pump PPC [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:ppc" }
Number Pump_Status1 "Pump Status 1 [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:status1" }
Number Pump_Status2 "Pump Status 2 [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:status2" }
Number Schedule1_Start "Schedule 1 start" (gPool) { channel = "pentair:controller:1:main:schedule1#start" }
Number Schedule1_End "Schedule 1 end" (gPool) { channel = "pentair:controller:1:main:schedule1#end" }
Number Schedule1_Type "Schedule 1 type" (gPool) { channel = "pentair:controller:1:main:schedule1#type" }
String Schedule1_String "Schedule 1 string" (gPool) { channel = "pentair:controller:1:main:schedule1#schedule" }
Number Schedule1_Circuit "Schedule 1 circuit" (gPool) { channel = "pentair:controller:1:main:schedule1#circuit" }
String Schedule1_Days "Schedule 1 days" (gPool) { channel = "pentair:controller:1:main:schedule1#days" }
```
Here is an example of a complete sitemap, saved as `pentair.sitemap`. Adjust the temperature values for metric if so desired.
### sitemap
```perl
sitemap pool label="Pool stuff" {
@ -194,13 +281,14 @@ sitemap pool label="Pool stuff" {
Switch item=Mode_PoolLight
Text item=Pool_Temp valuecolor=[>82="red",>77="orange",<=77="blue"]
Setpoint item=PoolSetPoint minValue=85 maxValue=103 step=1.0
Default item=PoolLightMode
Group item=gPool label="Advanced"
}
Frame label="Spa" {
Switch item=Mode_Spa
Switch item=Mode_SpaLight
Switch item=Mode_Jets
Text item=Pool_Temp valuecolor=[>82="red",>77="orange",<=77="blue"]
Text item=Spa_Temp valuecolor=[>82="red",>77="orange",<=77="blue"]
Setpoint item=SpaSetPoint minValue=85 maxValue=103 step=1.0
}
}
@ -208,12 +296,8 @@ sitemap pool label="Pool stuff" {
## References
Setting up RS485 and basic protocol - <https://www.sdyoung.com/home/decoding-the-pentair-easytouch-rs-485-protocol/>
ser2sock GitHub - <https://github.com/nutechsoftware/ser2sock>
nodejs-poolController - https://github.com/tagyoureit/nodejs-poolController
## Future Enhancements
- Add automatic discovery of devices on RS-485
- Add in IntelliBrite light color selection (need to capture protocol on system that has this)
- Add direct control of pump (non read-only channels)
- Fix heat active - not working on my system.

View File

@ -13,9 +13,4 @@
<artifactId>org.openhab.binding.pentair</artifactId>
<name>openHAB Add-ons :: Bundles :: Pentair Binding</name>
<properties>
<bnd.importpackage>gnu.io;version="[3.12,6)"</bnd.importpackage>
</properties>
</project>

View File

@ -12,11 +12,6 @@
*/
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;
@ -36,9 +31,10 @@ public class PentairBindingConstants {
public static final String SERIAL_BRIDGE = "serial_bridge";
// List of all Device Types
public static final String EASYTOUCH = "easytouch";
public static final String CONTROLLER = "controller";
public static final String INTELLIFLO = "intelliflo";
public static final String INTELLICHLOR = "intellichlor";
public static final String INTELLICHEM = "intellichem";
// List of all Bridge Thing Type UIDs
public static final ThingTypeUID IP_BRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, IP_BRIDGE);
@ -46,63 +42,137 @@ public class PentairBindingConstants {
// 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 CONTROLLER_THING_TYPE = new ThingTypeUID(BINDING_ID, CONTROLLER);
public static final ThingTypeUID INTELLICHLOR_THING_TYPE = new ThingTypeUID(BINDING_ID, INTELLICHLOR);
public static final ThingTypeUID INTELLICHEM_THING_TYPE = new ThingTypeUID(BINDING_ID, INTELLICHEM);
// 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 PARAMETER_ID = "id";
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";
// Controller Items
public static final String PROPERTY_CONTROLLER_FIRMWAREVERSION = "firmwareVersion";
public static final String PROPERTY_CONTROLLER_ID = "id";
public static final String EASYTOUCH_POOLSETPOINT = "poolsetpoint";
public static final String EASYTOUCH_SPASETPOINT = "spasetpoint";
public static final String CONTROLLER_CONFIGSYNCTIME = "synctime";
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 GROUP_CONTROLLER_STATUS = "status";
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 CHANNEL_CONTROLLER_AIRTEMPERATURE = "airtemperature";
public static final String CHANNEL_CONTROLLER_SOLARTEMPERATURE = "solartemperature";
public static final String CHANNEL_CONTROLLER_LIGHTMODE = "lightmode";
public static final String CHANNEL_CONTROLLER_SERVICEMODE = "servicemode";
public static final String CHANNEL_CONTROLLER_SOLARON = "solaron";
public static final String CHANNEL_CONTROLLER_HEATERON = "heateron";
public static final String CHANNEL_CONTROLLER_HEATERDELAY = "heaterdelay";
public static final String INTELLICHLOR_SALTOUTPUT = "saltoutput";
public static final String INTELLICHLOR_SALINITY = "salinity";
public static final String GROUP_CONTROLLER_POOLCIRCUIT = "pool";
public static final String GROUP_CONTROLLER_SPACIRCUIT = "spa";
public static final String GROUP_CONTROLLER_AUX1CIRCUIT = "aux1";
public static final String GROUP_CONTROLLER_AUX2CIRCUIT = "aux2";
public static final String GROUP_CONTROLLER_AUX3CIRCUIT = "aux3";
public static final String GROUP_CONTROLLER_AUX4CIRCUIT = "aux4";
public static final String GROUP_CONTROLLER_AUX5CIRCUIT = "aux5";
public static final String GROUP_CONTROLLER_AUX6CIRCUIT = "aux6";
public static final String GROUP_CONTROLLER_AUX7CIRCUIT = "aux7";
public static final String GROUP_CONTROLLER_AUX8CIRCUIT = "aux8";
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 CHANNEL_CONTROLLER_CIRCUITSWITCH = "switch";
public static final String CHANNEL_CONTROLLER_CIRCUITNAME = "name";
public static final String CHANNEL_CONTROLLER_CIRCUITFUNCTION = "function";
public static final String GROUP_CONTROLLER_FEATURE1 = "feature1";
public static final String GROUP_CONTROLLER_FEATURE2 = "feature2";
public static final String GROUP_CONTROLLER_FEATURE3 = "feature3";
public static final String GROUP_CONTROLLER_FEATURE4 = "feature4";
public static final String GROUP_CONTROLLER_FEATURE5 = "feature5";
public static final String GROUP_CONTROLLER_FEATURE6 = "feature6";
public static final String GROUP_CONTROLLER_FEATURE7 = "feature7";
public static final String GROUP_CONTROLLER_FEATURE8 = "feature8";
// List of heat group and items
public static final String GROUP_CONTROLLER_POOLHEAT = "poolheat";
public static final String GROUP_CONTROLLER_SPAHEAT = "spaheat";
public static final String CHANNEL_CONTROLLER_TEMPERATURE = "temperature";
public static final String CHANNEL_CONTROLLER_SETPOINT = "setpoint";
public static final String CHANNEL_CONTROLLER_HEATMODE = "heatmode";
// List of schedule group and items
public static final String GROUP_CONTROLLER_SCHEDULE = "schedule";
public static final String CHANNEL_CONTROLLER_SCHEDULESAVE = "save";
public static final String CHANNEL_CONTROLLER_SCHEDULESTRING = "schedule";
public static final String CHANNEL_CONTROLLER_SCHEDULETYPE = "type";
public static final String CHANNEL_CONTROLLER_SCHEDULECIRCUIT = "circuit";
public static final String CHANNEL_CONTROLLER_SCHEDULEDAYS = "days";
public static final String CHANNEL_CONTROLLER_SCHEDULESTART = "start";
public static final String CHANNEL_CONTROLLER_SCHEDULEEND = "end";
// List of Intellichlor channel ids
public static final String CHANNEL_INTELLICHLOR_PROPERTYVERSION = "version";
public static final String CHANNEL_INTELLICHLOR_PROPERTYMODEL = "model";
public static final String CHANNEL_INTELLICHLOR_SALTOUTPUT = "saltOutput";
public static final String CHANNEL_INTELLICHLOR_SALINITY = "salinity";
public static final String CHANNEL_INTELLICHLOR_OK = "ok";
public static final String CHANNEL_INTELLICHLOR_LOWFLOW = "lowFlow";
public static final String CHANNEL_INTELLICHLOR_LOWSALT = "lowSalt";
public static final String CHANNEL_INTELLICHLOR_VERYLOWSALT = "veryLowSalt";
public static final String CHANNEL_INTELLICHLOR_HIGHCURRENT = "highCurrent";
public static final String CHANNEL_INTELLICHLOR_CLEANCELL = "cleanCell";
public static final String CHANNEL_INTELLICHLOR_LOWVOLTAGE = "lowVoltage";
public static final String CHANNEL_INTELLICHLOR_LOWWATERTEMP = "lowWaterTemp";
public static final String CHANNEL_INTELLICHLOR_COMMERROR = "commError";
// IntelliChem Items
public static final String PROPERTY_INTELLICHEM_FIRMWAREVERSION = "firmwareVersion";
public static final String CHANNEL_INTELLICHEM_PHREADING = "phReading";
public static final String CHANNEL_INTELLICHEM_ORPREADING = "orpReading";
public static final String CHANNEL_INTELLICHEM_PHSETPOINT = "phSetPoint";
public static final String CHANNEL_INTELLICHEM_ORPSETPOINT = "orpSetPoint";
public static final String CHANNEL_INTELLICHEM_TANK1LEVEL = "tank1Level";
public static final String CHANNEL_INTELLICHEM_TANK2LEVEL = "tank2Level";
public static final String CHANNEL_INTELLICHEM_CALCIUMHARDNESS = "calciumHardness";
public static final String CHANNEL_INTELLICHEM_CYAREADING = "cyaReading";
public static final String CHANNEL_INTELLICHEM_ALKALINITY = "alkalinity";
public static final String CHANNEL_INTELLICHEM_PHDOSERTYPE = "phDoserType";
public static final String CHANNEL_INTELLICHEM_ORPDOSERTYPE = "orpDoserType";
public static final String CHANNEL_INTELLICHEM_PHDOSERSTATUS = "phDoserStatus";
public static final String CHANNEL_INTELLICHEM_ORPDOSERSTATUS = "orpDoserStatus";
public static final String CHANNEL_INTELLICHEM_PHDOSETIME = "phDoseTime";
public static final String CHANNEL_INTELLICHEM_ORPDOSETIME = "orpDoesTIme";
public static final String CHANNEL_INTELLICHEM_LSI = "lsi";
public static final String CHANNEL_INTELLICHEM_SALTLEVEL = "saltLevel";
public static final String CHANNEL_INTELLICHEM_ALARMWATERFLOW = "alarmWaterFlow";
public static final String CHANNEL_INTELLICHEM_ALARMPH = "alarmPh";
public static final String CHANNEL_INTELLICHEM_ALARMORP = "alarmOrp";
public static final String CHANNEL_INTELLICHEM_ALARMPHTANK = "alarmPhTank";
public static final String CHANNEL_INTELLICHEM_ALARMORPTANK = "alarmOrpTank";
public static final String CHANNEL_INTELLICHEM_ALARMPROBEFAULT = "alarmProbeFault";
public static final String CHANNEL_INTELLICHEM_WARNINGPHLOCKOUT = "warningPhLockout";
public static final String CHANNEL_INTELLICHEM_WARNINGPHDAILYLIMITREACHED = "warningPhDailyLimitReached";
public static final String CHANNEL_INTELLICHEM_WARNINGORPDAILYLIMITREACHED = "warningOrpDailyLimitReached";
public static final String CHANNEL_INTELLICHEM_WARNINGINVALIDSETUP = "warningInvalidSetup";
public static final String CHANNEL_INTELLICHEM_WARNINGCHLORINATORCOMMERROR = "warningChlorinatorCommError";
// List of all Intelliflo channel ids
public static final String CHANNEL_INTELLIFLO_RUN = "run";
public static final String CHANNEL_INTELLIFLO_POWER = "power";
public static final String CHANNEL_INTELLIFLO_RPM = "rpm";
public static final String INTELLIFLO_GPM = "gpm";
public static final String INTELLIFLO_ERROR = "error";
public static final String INTELLIFLO_STATUS1 = "status1";
public static final String INTELLIFLO_STATUS2 = "status2";
public static final String INTELLIFLO_TIMER = "timer";
public static final String INTELLIFLO_RUNPROGRAM = "runProgram";
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()));
public static final int DEFAULT_PENTAIR_ID = 34;
}

View File

@ -1,217 +0,0 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pentair.internal;
/**
* Generic class for the standard pentair package protocol. Includes helpers to generate checksum and extract key bytes
* from packet.
*
* @author Jeff James - initial contribution
*
*/
public class PentairPacket {
protected static final char[] HEXARRAY = "0123456789ABCDEF".toCharArray();
public static final int OFFSET = 2;
public static final int DEST = 0 + OFFSET;
public static final int SOURCE = 1 + OFFSET;
public static final int ACTION = 2 + OFFSET;
public static final int LENGTH = 3 + OFFSET;
public static final int STARTOFDATA = 4 + OFFSET;
protected boolean initialized;
public byte[] buf;
/**
* Constructor for PentairPacket basic packet.
*/
public PentairPacket() {
buf = new byte[6];
buf[0] = (byte) 0xA5;
}
/**
* Constructor for a PentairPackage with a byte array for the command. Typically used when generating a packet to
* send. Should include all bytes starting with A5. Do not include checksum bytes.
*
* @param buf Array of bytes to be used to populate packet.
*/
public PentairPacket(byte[] buf) {
this.buf = buf;
initialized = true;
}
/**
* Constructor to create a copy of PentairPacket p. Note references the same byte array as original. Used when
* coverting from a generic packet to a specialized packet.
*
* @param p PentairPacket to duplicate in new copy.
*/
public PentairPacket(PentairPacket p) {
this.buf = p.buf;
initialized = true;
}
/**
* Gets length of packet
*
* @return length of packet
*/
public int getLength() {
return buf[LENGTH];
}
/**
* Sets length of packet
*
* @param length length of packet
*/
public void setLength(int length) {
if (length > buf[LENGTH]) {
buf = new byte[length + 6];
}
buf[LENGTH] = (byte) length;
}
/**
* Gets action byte of packet
*
* @return action byte of packet
*/
public int getAction() {
return buf[ACTION];
}
/**
* Sets action byte of packet
*
* @param action
*/
public void setAction(int action) {
buf[ACTION] = (byte) action;
}
/**
* Gets source byte or packet
*
* @return source byte of packet
*/
public int getSource() {
return buf[SOURCE];
}
/**
* Sets source byte of packet
*
* @param source sets source byte of packet
*/
public void setSource(int source) {
buf[SOURCE] = (byte) source;
}
/**
* Gets destination byte of packet
*
* @return destination byte of packet
*/
public int getDest() {
return buf[DEST];
}
/**
* Sets destination byte of packet
*
* @param dest destination byte of packet
*/
public void setDest(int dest) {
buf[DEST] = (byte) dest;
}
/**
* Helper function to convert byte to hex representation
*
* @param b byte to re
* @return 2 charater hex string representing the byte
*/
public static String byteToHex(int b) {
char[] hexChars = new char[2];
hexChars[0] = HEXARRAY[b >>> 4];
hexChars[1] = HEXARRAY[b & 0x0F];
return new String(hexChars);
}
/**
* @param bytes array of bytes to convert to a hex string. Entire buf length is converted.
* @return hex string
*/
public static String bytesToHex(byte[] bytes) {
return bytesToHex(bytes, bytes.length);
}
/**
* @param bytes array of bytes to convert to a hex string.
* @param len Number of bytes to convert
* @return hex string
*/
public static String bytesToHex(byte[] bytes, int len) {
char[] hexChars = new char[len * 3];
for (int j = 0; j < len; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 3] = HEXARRAY[v >>> 4];
hexChars[j * 3 + 1] = HEXARRAY[v & 0x0F];
hexChars[j * 3 + 2] = ' ';
}
return new String(hexChars);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return bytesToHex(buf, getLength() + 6);
}
/**
* Used to extract a specific byte from the packet
*
* @param n number of byte (0 based)
* @return byte of packet
*/
public int getByte(int n) {
return buf[n];
}
/**
* Calculate checksum of the representative packet.
*
* @return checksum of packet
*/
public int calcChecksum() {
int checksum = 0, i;
for (i = 0; i < getLength() + 6; i++) {
checksum += buf[i] & 0xFF;
}
return checksum;
}
}

View File

@ -1,72 +0,0 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pentair.internal;
/**
* Pentair heat set point packet specialization of a PentairPacket. Includes public variables for many of the reverse
* engineered
* packet content.
*
* @author Jeff James - initial contribution
*
*/
public class PentairPacketHeatSetPoint extends PentairPacket {
protected static final int POOLTEMP = 5 + OFFSET;
protected static final int AIRTEMP = 6 + OFFSET;
protected static final int POOLSETPOINT = 7 + OFFSET;
protected static final int SPASETPOINT = 8 + OFFSET;
protected static final int HEATMODE = 9 + OFFSET;
protected static final int SOLARTEMP = 12 + OFFSET;
protected final String[] heatmodestrs = { "Off", "Heater", "Solar Pref", "Solar" };
/** pool temperature set point */
public int poolsetpoint;
/** pool heat mode - 0=Off, 1=Heater, 2=Solar Pref, 3=Solar */
public int poolheatmode;
/** pool heat mode as a string */
public String poolheatmodestr;
/** spa temperature set point */
public int spasetpoint;
/** spa heat mode - 0=Off, 1=Heater, 2=Solar Pref, 3=Solar */
public int spaheatmode;
/** spa heat mode as a string */
public String spaheatmodestr;
/**
* Constructor to create a specialized packet representing the generic packet. Note, the internal buffer array is
* not
* duplicated. Fills in public class members appropriate with the correct values.
*
* @param p Generic PentairPacket to create specific Status packet
*/
public PentairPacketHeatSetPoint(PentairPacket p) {
super(p);
poolsetpoint = p.buf[POOLSETPOINT];
poolheatmode = p.buf[HEATMODE] & 0x03;
poolheatmodestr = heatmodestrs[poolheatmode];
spasetpoint = p.buf[SPASETPOINT];
spaheatmode = (p.buf[HEATMODE] >> 2) & 0x03;
spaheatmodestr = heatmodestrs[spaheatmode];
}
/**
* Constructure to create an empty status packet
*/
public PentairPacketHeatSetPoint() {
super();
}
}

View File

@ -1,105 +0,0 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pentair.internal;
/**
* Pentair Intellichlor specialation of a PentairPacket. Includes public variables for many of the reverse engineered
* packet content. Note, Intellichlor packet is of a different format and all helper functions in the base PentairPacket
* may not apply.
*
* This packet can be a 3 or 4 data byte packet.
*
* 10 02 50 00 00 62 10 03
* 10 02 00 01 00 00 13 10 03
*
* @author Jeff James - initial contribution
*
*/
public class PentairPacketIntellichlor extends PentairPacket { // 29 byte packet format
protected static final int CMD = 3; // not sure what this is, needs to be 11 for SALT_OUTPUT or SALINITY to be valid
// 3 Length command
protected static final int SALTOUTPUT = 4;
// 4 Length command
protected static final int SALINITY = 4;
/** length of the packet - 3 or 4 data bytes */
protected int length;
/** for a saltoutput packet, represents the salt output percent */
public int saltoutput;
/** for a salinity packet, is value of salinity. Must be multiplied by 50 to get the actual salinity value. */
public int salinity;
/**
* Constructor for Intellichlor packet. Does not call super constructure since the Intellichlor packet is structure
* so differently
*
* @param buf
* @param length
*/
public PentairPacketIntellichlor(byte[] buf, int length) {
this.buf = buf;
this.length = length;
if (length == 3) {
saltoutput = buf[SALTOUTPUT];
} else if (length == 4) {
salinity = buf[SALINITY] & 0xFF; // make sure it is positive
}
}
/**
* Constructor for empty Intellichlor packet
*/
public PentairPacketIntellichlor() {
super();
}
/*
* (non-Javadoc)
*
* @see org.openhab.binding.pentair.PentairPacket#getLength()
*/
@Override
public int getLength() {
return length;
}
/*
* (non-Javadoc)
*
* @see org.openhab.binding.pentair.PentairPacket#setLength(int)
*/
@Override
public void setLength(int length) {
if (length != this.length) {
buf = new byte[length + 2];
}
this.length = length;
}
/**
* Gets the command byte for this packet
*
* @return command
*/
public int getCmd() {
return buf[CMD];
}
@Override
public String toString() {
return bytesToHex(buf, length + 5);
}
}

View File

@ -1,89 +0,0 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pentair.internal;
/**
* Pentair pump status packet specialation of a PentairPacket. Includes public variables for many of the reverse
* engineered packet content.
*
* @author Jeff James - initial contribution
*
*/
public class PentairPacketPumpStatus extends PentairPacket { // 15 byte packet format
protected static final int RUN = 4 + OFFSET;
protected static final int MODE = 5 + OFFSET; // Mode in pump status. Means something else in pump write/response?
protected static final int DRIVESTATE = 6 + OFFSET; // ?? Drivestate in pump status. Means something else in pump
// write/response
protected static final int WATTSH = 7 + OFFSET;
protected static final int WATTSL = 8 + OFFSET;
protected static final int RPMH = 9 + OFFSET;
protected static final int RPML = 10 + OFFSET;
protected static final int PPC = 11 + OFFSET; // ??
protected static final int ERR = 13 + OFFSET;
protected static final int TIMER = 14 + OFFSET; // ?? Have to explore
protected static final int HOUR = 17 + OFFSET;
protected static final int MIN = 18 + OFFSET;
/** pump is running */
public boolean run;
/** pump mode (1-4) */
public int mode;
/** pump drivestate - not sure what this specifically represents. */
public int drivestate;
/** pump power - in KW */
public int power;
/** pump rpm */
public int rpm;
/** pump ppc? */
public int ppc;
/** byte in packet indicating an error condition */
public int error;
/** current timer for pump */
public int timer;
/** hour or packet (based on Intelliflo time setting) */
public int hour;
/** minute of packet (based on Intelliflo time setting) */
public int min;
/**
* Constructor to create a specialized packet representing the generic packet. Note, the internal buffer array is
* not
* duplicated. Fills in public class members appropriate with the correct values.
*
* @param p Generic PentairPacket to create specific Status packet
*/
public PentairPacketPumpStatus(PentairPacket p) {
super(p);
run = (buf[RUN] == (byte) 0x0A);
mode = buf[MODE];
drivestate = buf[DRIVESTATE];
power = (buf[WATTSH] << 8) + buf[WATTSL];
rpm = (buf[RPMH] << 8) + buf[RPML];
ppc = buf[PPC];
error = buf[ERR];
timer = buf[TIMER];
hour = buf[HOUR];
min = buf[MIN];
}
/**
* Constructure to create an empty status packet
*/
public PentairPacketPumpStatus() {
super();
}
}

View File

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

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2024 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.actions;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* {@link PentairBaseActions } Abstract class for all Pentair actions classes
*
* @author Jeff James - Initial contribution
*
*/
@NonNullByDefault
public class PentairBaseActions {
@Nullable
private PentairWriter writer;
protected int id;
public void initialize(PentairWriter writer, int id) {
this.writer = writer;
this.id = id;
}
public PentairWriter getWriter() {
return Objects.requireNonNull(writer);
}
}

View File

@ -0,0 +1,289 @@
/**
* Copyright (c) 2010-2024 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.actions;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pentair.internal.handler.helpers.PentairControllerLightMode;
import org.openhab.binding.pentair.internal.handler.helpers.PentairControllerSchedule;
import org.openhab.binding.pentair.internal.handler.helpers.PentairHeatStatus;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairControllerActions } class to be used as base for all action commands to send on Pentair bus
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
public class PentairControllerActions extends PentairBaseActions {
private final Logger logger = LoggerFactory.getLogger(PentairControllerActions.class);
public enum ControllerCommand {
GET_STATUS(0x02, 0x02),
GET_CLOCK_SETTINGS(0xC5, 0x05),
SET_CLOCK_SETTINGS(0x85, -1),
SET_CIRCUIT_SWITCH(0x86, 0x01),
GET_LIGHT_GROUPS(0xE7, 0x27),
SET_LIGHT_MODE(0x60, 0x01),
GET_VALVES(0xDD, 0x1D),
GET_CIRCUIT_NAME_FUNCTION(0xCB, 0x0B),
SAVE_SCHEDULE(0x91, 0x01),
GET_SCHEDULE(0xD1, 0x11),
GET_SW_VERSION(0xFD, 0xFC),
DELAY_CANCEL(0x83, 0x01),
GET_HEAT_STATUS(0xC8, 0x08),
SET_HEAT_STATUS(0x88, 0x01);
public int send, response;
ControllerCommand(int send, int response) {
this.send = send;
this.response = response;
}
}
// Byte to use after 0xA5 in communicating to controller. Not sure why this changes,
// but it requires to be in sync and up-to-date
private int preambleByte = -1;
public void setPreambleByte(int preambleByte) {
this.preambleByte = preambleByte;
}
/**
* Method to turn on/off a circuit in response to a command from the framework
*
* @param circuit circuit number
* @param state
*/
public boolean setCircuitSwitch(int circuit, boolean state) {
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */,
(byte) ControllerCommand.SET_CIRCUIT_SWITCH.send, (byte) 0x02, (byte) circuit,
(byte) ((state) ? 1 : 0) };
if (!getWriter().writePacket(packet, ControllerCommand.SET_CIRCUIT_SWITCH.response, 1)) {
logger.trace("setCircuitSwitch: Timeout");
return false;
}
return true;
}
/**
* Method to request clock
*/
public boolean getClockSettings() { // A5 01 10 20 C5 01 00
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */,
(byte) ControllerCommand.GET_CLOCK_SETTINGS.send, (byte) 0x01, (byte) 0x00 };
if (!getWriter().writePacket(packet, ControllerCommand.GET_CLOCK_SETTINGS.response, 1)) {
logger.trace("getClockSettings: Timeout");
return false;
}
return true;
}
/**
* Method to request controller status
* Note the controller regularly sends out status, so this rarely needs to be called
*
*/
public boolean getStatus() { // A5 01 10 20 02 01 00
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */,
(byte) ControllerCommand.GET_STATUS.send, (byte) 0x01, (byte) 0x00 };
if (!getWriter().writePacket(packet, ControllerCommand.GET_STATUS.response, 1)) {
logger.trace("requestControllerStatus: Timeout");
return false;
}
return true;
}
public boolean getLightGroups() {
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */,
(byte) ControllerCommand.GET_LIGHT_GROUPS.send, (byte) 0x01, (byte) 0x00 };
if (!getWriter().writePacket(packet, ControllerCommand.GET_LIGHT_GROUPS.response, 1)) {
logger.trace("getLightGroups: Timeout");
return false;
}
return true;
}
public boolean setLightMode(int mode) {
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0x60,
(byte) 0x02, (byte) mode, (byte) 0x00 };
if (!getWriter().writePacket(packet, 0x01, 1)) {
logger.trace("setLightMode: Timeout");
return false;
}
return true;
}
public boolean setLightMode(PentairControllerLightMode lightMode) {
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */,
(byte) ControllerCommand.SET_LIGHT_MODE.send, (byte) 0x02, (byte) lightMode.getModeNumber(),
(byte) 0x00 };
if (!getWriter().writePacket(packet, ControllerCommand.SET_LIGHT_MODE.response, 1)) {
logger.trace("setLightMode: Timeout");
return false;
}
return true;
}
public boolean getValves() {
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */,
(byte) ControllerCommand.GET_VALVES.send, (byte) 0x01, (byte) 0x00 };
if (!getWriter().writePacket(packet, ControllerCommand.GET_VALVES.response, 1)) {
logger.trace("getValves: Timeout");
return false;
}
return true;
}
public boolean getCircuitNameFunction(int circuit) {
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */,
(byte) ControllerCommand.GET_CIRCUIT_NAME_FUNCTION.send, (byte) 0x01, (byte) circuit };
if (!getWriter().writePacket(packet, ControllerCommand.GET_CIRCUIT_NAME_FUNCTION.response, 1)) {
logger.trace("getCircuitNameFunction: Timeout");
return false;
}
return true;
}
public boolean getSchedule(int num) {
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */,
(byte) ControllerCommand.GET_SCHEDULE.send, (byte) 0x01, (byte) num };
if (!getWriter().writePacket(packet, ControllerCommand.GET_SCHEDULE.response, 1)) {
logger.trace("getSchedule: Timeout");
return false;
}
return true;
}
/**
* Method to update the schedule to the controller
*
* @param p
*/
public boolean saveSchedule(PentairControllerSchedule schedule) {
PentairStandardPacket p;
p = schedule.getWritePacket(id, preambleByte);
if (p == null) {
logger.debug("Schedule {} type is unknown.", id);
return false;
}
schedule.setDirty(false);
if (!getWriter().writePacket(p, 0x01, 1)) {
logger.trace("saveSchedule: Timeout");
return false;
}
return true;
}
public boolean getSWVersion() {
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */,
(byte) ControllerCommand.GET_SW_VERSION.send, (byte) 0x01, (byte) 0x00 };
if (!getWriter().writePacket(packet, ControllerCommand.GET_SW_VERSION.response, 1)) {
logger.trace("requestSWVersion: Timeout");
return false;
}
return true;
}
public boolean cancelDelay() {
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */,
(byte) ControllerCommand.DELAY_CANCEL.send, (byte) 0x01, (byte) 0x00 };
if (!getWriter().writePacket(packet, ControllerCommand.DELAY_CANCEL.response, 1)) {
logger.trace("cancelDelay: Timeout");
return false;
}
return true;
}
/**
* Method to set clock
*/
public boolean setClockSettings(int hour, int min, int dow, int day, int month, int year) {
// A5 01 10 20 85 08 0D 2A 02 1D 04 11 00 00
if (hour > 23) {
throw new IllegalArgumentException("hour not in range [0..23]: " + hour);
}
if (min > 59) {
throw new IllegalArgumentException("hour not in range [0..59]: " + min);
}
if (dow > 7 || dow < 1) {
throw new IllegalArgumentException("hour not in range [1..7]: " + dow);
}
if (day > 31 || day < 1) {
throw new IllegalArgumentException("hour not in range [1..31]: " + day);
}
if (month > 12 || month < 1) {
throw new IllegalArgumentException("hour not in range [1..12]: " + month);
}
if (year > 99) {
throw new IllegalArgumentException("hour not in range [0..99]: " + year);
}
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */,
(byte) ControllerCommand.SET_CLOCK_SETTINGS.send, (byte) 0x08, (byte) hour, (byte) min, (byte) dow,
(byte) day, (byte) month, (byte) year, (byte) 0x00, (byte) 0x00 };
getWriter().writePacket(packet);
return true;
}
public boolean getHeatStatus() { // A5 01 10 20 C8 01 00
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */,
(byte) ControllerCommand.GET_HEAT_STATUS.send, (byte) 0x01, (byte) 0 };
if (!getWriter().writePacket(packet, ControllerCommand.GET_HEAT_STATUS.response, 1)) {
logger.trace("getHeatStatus: Timeout");
return false;
}
return true;
}
/**
* Method to set heat point for pool (true) of spa (false)
*
* @param Pool pool=true, spa=false
* @param temp
*/
public boolean setHeatStatus(PentairHeatStatus pentairHeatStatus) {
// [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 heatmode = (pentairHeatStatus.spaHeatMode.getCode() << 2) | pentairHeatStatus.poolHeatMode.getCode();
byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */,
(byte) ControllerCommand.SET_HEAT_STATUS.send, (byte) 0x04, (byte) pentairHeatStatus.poolSetPoint,
(byte) pentairHeatStatus.spaSetPoint, (byte) heatmode, (byte) 0 };
if (!getWriter().writePacket(packet, ControllerCommand.SET_HEAT_STATUS.response, 1)) {
logger.trace("setHeatStatus: Timeout");
return false;
}
return true;
}
}

View File

@ -0,0 +1,167 @@
/**
* Copyright (c) 2010-2024 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.actions;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pentair.internal.handler.PentairIntelliFloHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairIntelliFloActions } class to be used as base for all action commands to send on Pentair bus
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
public class PentairIntelliFloActions extends PentairBaseActions {
private final Logger logger = LoggerFactory.getLogger(PentairIntelliFloActions.class);
public enum PumpCommand {
GET_STATUS(0x07, 0x07),
SET_LOCAL_OR_REMOTE_CONTROL(0x04, 0x04),
SET_ON_OR_OFF(0x06, 0x06),
SET_RPM(0x01, 0x01),
SET_RUN_PROGRAM(0x01, 0x06);
public int send, response;
PumpCommand(int send, int response) {
this.send = send;
this.response = response;
}
}
@Nullable
private PentairIntelliFloHandler handler;
public void setHandler(PentairIntelliFloHandler handler) {
this.handler = handler;
}
public PentairIntelliFloHandler getHandler() {
return Objects.requireNonNull(handler);
}
/* Commands to send to IntelliFlo */
private boolean coreGetStatus() {
byte[] packet = { (byte) 0xA5, (byte) 0x00, (byte) id, (byte) 0x00 /* source */,
(byte) PumpCommand.GET_STATUS.send, (byte) 0x00 };
if (!getWriter().writePacket(packet, PumpCommand.GET_STATUS.response, 1)) {
logger.debug("sendRequestStatus: Timeout");
return false;
}
return true;
}
public boolean getStatus() {
boolean success = setLocalORRemoteControl(false);
success &= coreGetStatus();
return success;
}
public boolean setLocalORRemoteControl(boolean bLocal) {
byte[] packet = { (byte) 0xA5, (byte) 0x00, (byte) id, (byte) 0x00 /* source */,
(byte) PumpCommand.SET_LOCAL_OR_REMOTE_CONTROL.send, (byte) 0x01,
(bLocal) ? (byte) 0x00 : (byte) 0xFF };
if (!getWriter().writePacket(packet, PumpCommand.SET_LOCAL_OR_REMOTE_CONTROL.response, 1)) {
logger.debug("sendLocalOrRemoteControl: Timeout");
return false;
}
return true;
}
public boolean coreSetOnOROff(boolean bOn) {
byte[] packet = { (byte) 0xA5, (byte) 0x00, (byte) id, (byte) 0x00 /* source */,
(byte) PumpCommand.SET_ON_OR_OFF.send, (byte) 0x01, (bOn) ? (byte) 0x0A : (byte) 0x04 };
getHandler().setRunMode(bOn);
if (!getWriter().writePacket(packet, PumpCommand.SET_ON_OR_OFF.response, 1)) {
logger.trace("sendPumpOnOROff: Timeout");
return false;
}
return true;
}
public boolean setOnOrOff(boolean bOn) {
boolean success = setLocalORRemoteControl(false);
success &= coreSetOnOROff(bOn);
success &= coreGetStatus();
success &= setLocalORRemoteControl(true);
return success;
}
// sendPumpRPM - low-level call to send to pump the RPM command
private boolean coreSetRPM(int rpm) {
int rpmH, rpmL;
rpmH = rpm / 256;
rpmL = rpm % 256;
byte[] packet = { (byte) 0xA5, (byte) 0x00, (byte) id, (byte) 0x00 /* source */,
(byte) PumpCommand.SET_RPM.send, (byte) 0x04, (byte) 0x02, (byte) 0xC4, (byte) rpmH, (byte) rpmL };
if (rpm < 400 || rpm > 3450) {
throw new IllegalArgumentException("rpm not in range [400..3450]: " + rpm);
}
getHandler().setRunMode(true);
if (!getWriter().writePacket(packet, PumpCommand.SET_RPM.response, 1)) {
logger.debug("sendPumpRPM: timeout");
return false;
}
return true;
}
// setPumpRPM - high-level call that includes wrapper commands and delay functions
public boolean setRPM(int rpm) {
boolean success = setLocalORRemoteControl(false);
success &= coreSetRPM(rpm);
success &= coreSetOnOROff(true);
success &= coreGetStatus();
success &= setLocalORRemoteControl(true);
return success;
}
// sendRunProgram - low-level call to send the command to pump
private boolean coreSetRunProgram(int program) {
if (program < 1 || program > 4) {
return false;
}
byte[] packet = { (byte) 0xA5, (byte) 0x00, (byte) id, (byte) 0x00 /* source */,
(byte) PumpCommand.SET_RUN_PROGRAM.send, (byte) 0x04, (byte) 0x03, (byte) 0x21, (byte) 0x00,
(byte) (program << 3) };
getHandler().setRunMode(true);
if (!getWriter().writePacket(packet, PumpCommand.SET_RUN_PROGRAM.response, 1)) {
logger.debug("sendRunProgram: Timeout");
return false;
}
return true;
}
// setRunProgram - high-level call to run program - including wrapper calls
public boolean setRunProgram(int program) {
boolean success = setLocalORRemoteControl(false);
success &= coreSetRunProgram(program);
success &= coreSetOnOROff(true);
success &= coreGetStatus();
success &= setLocalORRemoteControl(true);
return success;
}
}

View File

@ -0,0 +1,175 @@
/**
* Copyright (c) 2010-2024 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.actions;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pentair.internal.parser.PentairBasePacket;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairWriter } class to be used as base for all action commands to send on Pentair bus.
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
public class PentairWriter {
private final Logger logger = LoggerFactory.getLogger(PentairWriter.class);
private @Nullable OutputStream outputStream;
private int sourceId;
// lock used to prevent multiple commands to be sent at same time. Condition waitAck will be cleared when the
// appropriate response has been received thus making writePacket blocking.
private final ReentrantLock lock = new ReentrantLock();
private Condition waitAck = lock.newCondition();
private int ackResponse = -1;
private CallbackWriter owner;
public interface CallbackWriter {
void writerFailureCallback();
}
public PentairWriter(CallbackWriter owner) {
this.owner = owner;
}
public void initialize(OutputStream outputStream, int sourceId) {
this.outputStream = outputStream;
this.sourceId = sourceId;
}
public int getSourceId() {
return sourceId;
}
public OutputStream getOutputStream() {
return Objects.requireNonNull(outputStream, "outputStream is null");
}
/**
* Method to write a byte array to the bus. This method is blocking until a valid response is received, or a
* failure.
*
* @param packet is the byte array to write to the bus
*/
public boolean writePacket(byte[] packet) {
return writePacket(packet, -1, 0);
}
/**
* Method to write a byte array to the bus. This method is blocking until a valid response is received, or a
* failure.
*
* @param packet is the byte array to write to the bus
* @param response is the response to wait for
* @param retries is the number of retries
*/
public boolean writePacket(byte[] packet, int response, int retries) {
PentairStandardPacket p = new PentairStandardPacket(packet);
return writePacket(p, response, retries);
}
/**
* Method to write a PentairStandardPackage to the bus. This method is blocking until a valid response is received,
* or a failure.
*
* @param p {@link PentairStandardPacket} to write
*/
public boolean writePacket(PentairStandardPacket p) {
return writePacket(p, -1, 0);
}
/**
* Method to write a package on the Pentair bus. Will add source ID and checksum to bytes written. This method is
* blocking until a valid response is received, or a failure.
*
* @param p {@link PentairStandardPacket} to write
* @param response is the expected response type to wait for from this package send. The Lock will
* clear when a response of this type is received and ackReponse is called.
* @param retries is number of retries before a time-out
*/
public boolean writePacket(PentairStandardPacket p, int response, int retries) {
boolean success = true;
OutputStream outputStream;
outputStream = getOutputStream();
try {
byte[] buf;
int nRetries = retries;
p.setByte(PentairStandardPacket.SOURCE, (byte) sourceId);
buf = p.wrapPacketToSend();
lock.lock();
this.ackResponse = response;
do {
logger.trace("[{}] Writing packet: {}", p.getDest(), PentairBasePacket.toHexString(buf));
outputStream.write(buf, 0, buf.length);
outputStream.flush();
if (response != -1) {
logger.trace("[{}] writePacket: wait for ack (response: {}, retries: {})", p.getDest(), response,
nRetries);
success = waitAck.await(1000, TimeUnit.MILLISECONDS); // success will be false if timeout
nRetries--;
}
} while (!success && (nRetries >= 0));
} catch (IOException e) {
owner.writerFailureCallback();
return false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
owner.writerFailureCallback();
return false;
} finally {
lock.unlock();
}
if (!success) {
logger.trace("[{}] writePacket: timeout", p.getDest());
}
return success;
}
/**
* Method to acknowledge an ack or response packet has been sent
*
* @param cmdresponse is the command that was seen as a return. This is validate against that this was the response
* before signally a return.
*/
public void ackResponse(int response) {
if (response != ackResponse) {
return;
}
lock.lock();
waitAck.signalAll();
lock.unlock();
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2024 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 static org.openhab.binding.pentair.internal.PentairBindingConstants.DEFAULT_PENTAIR_ID;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PentairBaseBridgeConfig } class contains the base parameters in all bridges
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
public class PentairBaseBridgeConfig {
/** ID to use when sending commands on the Pentair RS485 bus. */
public int id = DEFAULT_PENTAIR_ID;
/** enable automatic discovery */
public boolean discovery = true;
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2024 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.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PentairBaseThingConfig } class contains configuration parameters for all Pentair child things.
*
* @author Jeff James - initial contribution
*
*/
@NonNullByDefault
public class PentairBaseThingConfig {
/** ID of thing on the Pentair RS485 bus. */
public int id;
}

View File

@ -12,23 +12,16 @@
*/
package org.openhab.binding.pentair.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Configuration parameters for IP Bridge
* The {@link PentairIPBridgeConfig } class contains the parameters for IP Bridge
*
* @author Jeff James - initial contribution
*
*/
@NonNullByDefault
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 getClass().getSimpleName() + "{ address=" + address + ", port=" + port + ", id=" + id + "}";
}
public String address = "";
public int port = 10000;
}

View File

@ -12,20 +12,16 @@
*/
package org.openhab.binding.pentair.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Configuration parameters for Serial Bridge
* The {@link PentairSerialBridgeConfig } class contains the configuration parameters for Serial Bridge
*
* @author Jeff James - initial contribution
*
*/
@NonNullByDefault
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 getClass().getSimpleName() + "{ serialPort=" + serialPort + ", id=" + id + "}";
}
public String serialPort = "";
}

View File

@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2024 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.discovery;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
import java.util.Objects;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pentair.internal.handler.PentairBaseBridgeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairDiscoveryService} handles discovery of devices as they are identified by the bridge handler.
* Requests from the framework to startScan() are ignored, since no active scanning is possible.
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
public class PentairDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
public static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES = Set.of(CONTROLLER_THING_TYPE,
INTELLIFLO_THING_TYPE, INTELLICHLOR_THING_TYPE, INTELLICHEM_THING_TYPE);
private final Logger logger = LoggerFactory.getLogger(PentairDiscoveryService.class);
private @Nullable PentairBaseBridgeHandler bridgeHandler;
public PentairDiscoveryService() throws IllegalArgumentException {
super(DISCOVERABLE_THING_TYPES, 0, false);
}
@Override
public void activate() {
super.activate(null);
}
@Override
public void deactivate() {
super.deactivate();
}
@Override
protected void startScan() {
// Ignore start scan requests
}
public void notifyDiscoveredThing(ThingTypeUID thingTypeUID, int id, String label) {
PentairBaseBridgeHandler bridgeHandler = Objects.requireNonNull(this.bridgeHandler,
"Discovery with null bridge handler.");
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, label);
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withProperty(PARAMETER_ID, id).withRepresentationProperty(PARAMETER_ID).withLabel(label).build();
thingDiscovered(result);
logger.debug("Discovered Thing {}", thingUID);
}
@Override
public void setThingHandler(ThingHandler handler) {
if (handler instanceof PentairBaseBridgeHandler baseBridgeHandler) {
this.bridgeHandler = baseBridgeHandler;
baseBridgeHandler.setDiscoveryService(this);
} else {
this.bridgeHandler = null;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return this.bridgeHandler;
}
}

View File

@ -10,50 +10,75 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pentair.internal;
package org.openhab.binding.pentair.internal.factory;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
import org.openhab.binding.pentair.internal.handler.PentairEasyTouchHandler;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pentair.internal.handler.PentairControllerHandler;
import org.openhab.binding.pentair.internal.handler.PentairIPBridgeHandler;
import org.openhab.binding.pentair.internal.handler.PentairIntelliChemHandler;
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.io.transport.serial.SerialPortManager;
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.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link PentairHandlerFactory} is responsible for creating things and thing
* The {@link PentairHandlerFactory} is responsible for creating thing
* handlers.
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.pentair")
public class PentairHandlerFactory extends BaseThingHandlerFactory {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(IP_BRIDGE_THING_TYPE,
SERIAL_BRIDGE_THING_TYPE, CONTROLLER_THING_TYPE, INTELLIFLO_THING_TYPE, INTELLICHLOR_THING_TYPE,
INTELLICHEM_THING_TYPE);
private final SerialPortManager serialPortManager;
@Activate
public PentairHandlerFactory(final @Reference SerialPortManager serialPortManager) {
// Obtain the serial port manager service using an OSGi reference
this.serialPortManager = serialPortManager;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected ThingHandler createHandler(Thing thing) {
protected @Nullable 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);
return new PentairSerialBridgeHandler((Bridge) thing, serialPortManager);
} else if (thingTypeUID.equals(CONTROLLER_THING_TYPE)) {
return new PentairControllerHandler(thing);
} else if (thingTypeUID.equals(INTELLIFLO_THING_TYPE)) {
return new PentairIntelliFloHandler(thing);
} else if (thingTypeUID.equals(INTELLICHLOR_THING_TYPE)) {
return new PentairIntelliChlorHandler(thing);
} else if (thingTypeUID.equals(INTELLICHEM_THING_TYPE)) {
return new PentairIntelliChemHandler(thing);
}
return null;

View File

@ -12,57 +12,94 @@
*/
package org.openhab.binding.pentair.internal.handler;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.INTELLIFLO_THING_TYPE;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
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.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pentair.internal.actions.PentairWriter;
import org.openhab.binding.pentair.internal.actions.PentairWriter.CallbackWriter;
import org.openhab.binding.pentair.internal.config.PentairBaseBridgeConfig;
import org.openhab.binding.pentair.internal.discovery.PentairDiscoveryService;
import org.openhab.binding.pentair.internal.parser.PentairIntelliChlorPacket;
import org.openhab.binding.pentair.internal.parser.PentairParser;
import org.openhab.binding.pentair.internal.parser.PentairParser.CallbackPentairParser;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
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.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
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.
* The {@link PentairBaseBridgeHandler } 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 {
@NonNullByDefault
public abstract class PentairBaseBridgeHandler extends BaseBridgeHandler
implements CallbackPentairParser, CallbackWriter {
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<>();
private final PentairParser parser = new PentairParser();
private final PentairWriter actions = new PentairWriter(this);
// input/output stream must be assigned by sub-class in connect method
@Nullable
private BufferedInputStream inputStream;
@Nullable
private BufferedOutputStream outputStream;
private @Nullable PentairDiscoveryService discoveryService;
private @Nullable Thread parserThread;
private @Nullable ScheduledFuture<?> monitorIOJob;
private PentairBaseBridgeConfig config = new PentairBaseBridgeConfig();
/** array to keep track of IDs seen on the Pentair bus that are not configured yet */
private final Set<Integer> unregistered = new HashSet<Integer>();
final Map<Integer, @Nullable PentairBaseThingHandler> equipment = new HashMap<>();
// keep accessible a static reference to the bridge. This binding will only work with a single bridge
@Nullable
private static Bridge bridge;
@Nullable
public static Bridge getSingleBridge() {
return bridge;
}
public void setDiscoveryService(PentairDiscoveryService discoveryService) {
this.discoveryService = discoveryService;
}
/**
* Gets pentair bus id
@ -70,64 +107,209 @@ public abstract class PentairBaseBridgeHandler extends BaseBridgeHandler {
* @return id
*/
public int getId() {
return id;
return config.id;
}
private enum ParserState {
WAIT_SOC,
CMD_PENTAIR,
CMD_INTELLICHLOR
public PentairWriter getBaseActions() {
return actions;
}
/**
* Constructor
*
* @param bridge
*/
PentairBaseBridgeHandler(Bridge bridge) {
super(bridge);
parser.setCallback(this);
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(PentairDiscoveryService.class);
}
@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.");
this.config = getConfigAs(PentairBaseBridgeConfig.class);
connect();
if (PentairBaseBridgeHandler.bridge != null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.bridge-duplicate");
return;
}
pollingjob = scheduler.scheduleWithFixedDelay(new PumpStatus(), 10, 120, TimeUnit.SECONDS);
PentairBaseBridgeHandler.bridge = this.getThing();
updateStatus(ThingStatus.UNKNOWN);
this.monitorIOJob = scheduler.scheduleWithFixedDelay(this::monitorIO, 60, 30, TimeUnit.SECONDS);
baseConnect();
}
@Override
public void dispose() {
logger.debug("Handler disposed.");
pollingjob.cancel(true);
disconnect();
PentairBaseBridgeHandler.bridge = null;
ScheduledFuture<?> monitorIOJob = this.monitorIOJob;
if (monitorIOJob != null) {
monitorIOJob.cancel(true);
}
baseDisconnect();
}
/*
* Custom function to call during initialization to notify the bridge. childHandlerInitialized is not called
* until the child thing actually goes to the ONLINE status.
*/
public void childHandlerInitializing(ThingHandler childHandler, Thing childThing) {
if (childHandler instanceof PentairBaseThingHandler baseThingHandler) {
equipment.put(baseThingHandler.getPentairID(), baseThingHandler);
unregistered.remove(baseThingHandler.getPentairID());
}
}
@Override
public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
if (childHandler instanceof PentairBaseThingHandler baseThingHandler) {
equipment.remove(baseThingHandler.getPentairID());
}
}
/**
* Abstract method for creating connection. Must be implemented in subclass.
* Return 0 if all goes well. Must call setInputStream and setOutputStream before exciting.
*/
protected abstract void connect();
protected abstract boolean connect();
/**
* Abstract method for disconnect. Must be implemented in subclass
*/
protected abstract void disconnect();
private void baseConnect() {
if (getThing().getStatus() == ThingStatus.ONLINE) {
return;
}
// montiorIOJob will only start after a successful connection
if (monitorIOJob == null) {
monitorIOJob = scheduler.scheduleWithFixedDelay(this::monitorIO, 60, 30, TimeUnit.SECONDS);
}
if (!connect()) {
// if connect() sets to offline, preserve the StatusDetail\
if (getThing().getStatus() != ThingStatus.OFFLINE) {
updateStatus(ThingStatus.OFFLINE);
}
return;
}
Thread parserThread = new Thread(parser, "OH-pentair-" + this.getThing().getUID() + "-parser");
this.parserThread = parserThread;
parserThread.setDaemon(true);
parserThread.start();
if (inputStream == null || outputStream == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.communication-error.iostream-error ");
return;
}
updateStatus(ThingStatus.ONLINE);
}
private void baseDisconnect() {
// Preserve OFFLINE status detail if already OFFLINE
if (getThing().getStatus() != ThingStatus.OFFLINE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
Thread parserThread = this.parserThread;
if (parserThread != null) {
try {
parserThread.interrupt();
parserThread.join(3000); // wait for thread to complete
} catch (InterruptedException e) {
// do nothing
}
parserThread = null;
}
BufferedInputStream reader = this.inputStream;
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
logger.debug("setInputStream: Exception error while closing: {}", e.getMessage());
}
}
BufferedOutputStream writer = this.outputStream;
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
logger.debug("setOutputStream: Exception error while closing: {}", e.getMessage());
}
}
disconnect();
}
public void setInputStream(InputStream inputStream) {
BufferedInputStream reader = this.inputStream;
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
logger.trace("setInputStream: Exception error while closing: {}", e.getMessage());
}
}
this.inputStream = new BufferedInputStream(inputStream);
parser.setInputStream(inputStream);
}
public void setOutputStream(OutputStream outputStream) {
BufferedOutputStream writer = this.outputStream;
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
logger.trace("setOutputStream: Exception error while closing: {}", e.getMessage());
}
}
writer = new BufferedOutputStream(outputStream);
this.outputStream = writer;
actions.initialize(writer, getId());
}
// method to poll to try and reconnect upon being disconnected. Note this should only be started on an initial
private void monitorIO() {
ThingStatus thingStatus = getThing().getStatus();
if (thingStatus == ThingStatus.ONLINE) {
// Check if parser thread has terminated and if it has reconnect. This will take down the interface and
// restart the interface.
Thread parserThread = Objects.requireNonNull(this.parserThread);
if (!parserThread.isAlive()) {
baseDisconnect();
baseConnect();
}
} else if (thingStatus == ThingStatus.OFFLINE) {
baseConnect();
}
}
/**
* Helper function to find a Thing assigned to this bridge with a specific pentair bus id.
*
* @param id Pentiar bus id
* @param id Pentair bus id
* @return Thing object. null if id is not found.
*/
public Thing findThing(int id) {
public @Nullable Thing findThing(int id) {
List<Thing> things = getThing().getThings();
for (Thing t : things) {
@ -141,313 +323,116 @@ public abstract class PentairBaseBridgeHandler extends BaseBridgeHandler {
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() {
public @Nullable PentairControllerHandler findController() {
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 };
for (Thing t : things) {
PentairBaseThingHandler handler = (PentairBaseThingHandler) t.getHandler();
PentairPacket p = new PentairPacket(packet);
if (handler instanceof PentairControllerHandler controllerHandler) {
return controllerHandler;
}
}
return null;
}
public @Nullable PentairIntelliChlorHandler findIntellichlor() {
List<Thing> things = getThing().getThings();
for (Thing t : things) {
if (!t.getThingTypeUID().equals(INTELLIFLO_THING_TYPE)) {
continue;
}
PentairBaseThingHandler handler = (PentairBaseThingHandler) t.getHandler();
p.setDest(((PentairIntelliFloHandler) t.getHandler()).id);
writePacket(p);
try {
Thread.sleep(300); // make sure each pump has time to respond
} catch (InterruptedException e) {
break;
}
}
if (handler instanceof PentairIntelliChlorHandler intelliChlorHandler) {
return intelliChlorHandler;
}
}
/**
* 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 {
return null;
}
@Override
public void run() {
logger.debug("parser thread started");
byte[] buf = new byte[40];
int c;
int chksum, i, length;
Thing thing;
public void onPentairPacket(PentairStandardPacket p) {
PentairBaseThingHandler thinghandler;
ParserState parserstate = ParserState.WAIT_SOC;
int source = p.getSource();
thinghandler = equipment.get(source);
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;
int sourceType = (source >> 4);
if (sourceType == 0x02) { // control panels are 0x2*, don't treat as an
// unregistered device
logger.debug("[{}] Command from control panel device: {}", source, p);
} else if (!unregistered.contains(source)) { // if not yet seen discover
PentairDiscoveryService discoveryService = this.discoveryService;
if (discoveryService != null) {
if (sourceType == 0x01) { // controller
PentairControllerHandler handler = this.findController();
if (handler == null) { // only register one controller
if (config.discovery) {
discoveryService.notifyDiscoveredThing(CONTROLLER_THING_TYPE, source, CONTROLLER);
}
}
} else if (sourceType == 0x06) {
if (config.discovery) {
int pumpid = (source & 0x04) + 1;
discoveryService.notifyDiscoveredThing(INTELLIFLO_THING_TYPE, source, "pump" + pumpid);
}
} else if (sourceType == 0x09) {
if (config.discovery) {
discoveryService.notifyDiscoveredThing(INTELLICHEM_THING_TYPE, source, INTELLICHEM);
}
}
logger.trace("Received pentair command: {}", p);
logger.debug("[{}] First command from unregistered device: {}", source, p);
unregistered.add(source);
}
} else {
logger.debug("[{}] Subsequent command from unregistered device: {}", source, p);
}
} else {
logger.trace("[{}] Received pentair command: {}", source, 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
actions.ackResponse(p.getAction());
}
}
chksum = 0;
for (i = 0; i < length + 2; i++) {
chksum += buf[i] & 0xFF;
}
@Override
public void onIntelliChlorPacket(PentairIntelliChlorPacket p) {
PentairBaseThingHandler thinghandler;
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;
}
thinghandler = equipment.get(0);
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;
// Only register if the packet is sent from chlorinator (i.e. action=0x12)
int dest = p.getByte(PentairIntelliChlorPacket.DEST);
if (!unregistered.contains(0) && p.getByte(PentairIntelliChlorPacket.ACTION) == 0x12) {
PentairDiscoveryService discoveryService = this.discoveryService;
if (config.discovery && discoveryService != null) {
discoveryService.notifyDiscoveredThing(INTELLICHLOR_THING_TYPE, 0, INTELLICHLOR);
logger.debug("[{}] First command from unregistered Intellichlor: {}", dest, p);
unregistered.add(0);
}
} else {
logger.debug("[{}] Subsequent command from unregistered Intellichlor: {}", dest, p);
}
return;
}
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.
thinghandler.processPacketFrom(p);
}
logger.debug("msg reader thread exited");
}
@Override
public void writerFailureCallback() {
baseDisconnect();
}
/**
* 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());
}
@Override
public void parserFailureCallback() {
baseDisconnect();
}
}

View File

@ -12,37 +12,182 @@
*/
package org.openhab.binding.pentair.internal.handler;
import org.openhab.binding.pentair.internal.PentairPacket;
import java.util.Collection;
import java.util.List;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pentair.internal.config.PentairBaseThingConfig;
import org.openhab.binding.pentair.internal.parser.PentairBasePacket;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract class for all Pentair Things.
* {@link PentairBaseThingHandler } Abstract class for all Pentair thing handlers.
*
* @author Jeff James - Initial contribution
*
*/
@NonNullByDefault
public abstract class PentairBaseThingHandler extends BaseThingHandler {
/** ID of Thing on Pentair bus */
protected int id;
@SuppressWarnings("unused")
private final Logger logger = LoggerFactory.getLogger(PentairBaseThingHandler.class);
private PentairBaseThingConfig config = new PentairBaseThingConfig();
// waitStatusForOnline indicates whether the device is waiting to go fully online until after a first packet is
// received
protected boolean waitStatusForOnline = false;
public PentairBaseThingHandler(Thing thing) {
super(thing);
}
/**
* Gets Pentair bus ID of Thing
*
* @return
*/
@Override
public void initialize() {
this.config = getConfigAs(PentairBaseThingConfig.class);
PentairBaseBridgeHandler bh = getBridgeHandler();
if (bh == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.bridge-missing");
return;
}
if (bh.equipment.get(config.id) != null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.duplicate-id");
return;
}
bh.childHandlerInitializing(this, this.getThing());
goOnline();
updateStatus(ThingStatus.UNKNOWN);
}
public void goOnline() {
waitStatusForOnline = true;
}
public void finishOnline() {
waitStatusForOnline = false;
updateStatus(ThingStatus.ONLINE);
}
public void goOffline(ThingStatusDetail detail) {
updateStatus(ThingStatus.OFFLINE, detail);
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
goOffline(ThingStatusDetail.BRIDGE_OFFLINE);
} else if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
waitStatusForOnline = false;
goOnline();
}
}
public int getPentairID() {
return id;
return config.id;
}
@Nullable
public PentairBaseBridgeHandler getBridgeHandler() {
// make sure bridge exists and is online
Bridge bridge = this.getBridge();
if (bridge == null) {
return null;
}
PentairBaseBridgeHandler bh = (PentairBaseBridgeHandler) bridge.getHandler();
if (bh == null) {
return null;
}
return bh;
}
/**
* Abstract function to be implemented by Thing to dispose/parse a received packet
* Helper function to update channel.
*/
public void updateChannel(ChannelUID channel, boolean value) {
updateState(channel, OnOffType.from(value));
}
public void updateChannel(ChannelUID channel, int value) {
updateState(channel, new DecimalType(value));
}
public void updateChannel(ChannelUID channel, double value) {
updateState(channel, new DecimalType(value));
}
public void updateChannel(ChannelUID channel, String value) {
updateState(channel, new StringType(value));
}
public void updateChannel(ChannelUID channel, Number value, Unit<?> unit) {
updateState(channel, new QuantityType<>(value, unit));
}
public void refreshAllChannels() {
List<Channel> channels = getThing().getChannels();
refreshChannels(channels);
}
public void refreshGroupChannels(String group) {
List<Channel> channels = getThing().getChannelsOfGroup(group);
refreshChannels(channels);
}
public void refreshChannels(Collection<Channel> channels) {
ThingHandler handler = getThing().getHandler();
if (handler == null) {
return;
}
for (Channel channel : channels) {
ChannelUID uid = channel.getUID();
handler.handleCommand(uid, RefreshType.REFRESH);
}
}
public void refreshChannelsFromUIDs(Collection<ChannelUID> channelUIDs) {
ThingHandler handler = getThing().getHandler();
if (handler == null) {
return;
}
for (ChannelUID channelUID : channelUIDs) {
handler.handleCommand(channelUID, RefreshType.REFRESH);
}
}
/**
* Abstract function to be implemented by Thing to parse a received packet
*
* @param p
*/
public abstract void processPacketFrom(PentairPacket p);
public abstract void processPacketFrom(PentairBasePacket p);
}

View File

@ -0,0 +1,817 @@
/**
* Copyright (c) 2010-2024 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.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.measure.Unit;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pentair.internal.actions.PentairControllerActions;
import org.openhab.binding.pentair.internal.handler.helpers.PentairControllerCircuit;
import org.openhab.binding.pentair.internal.handler.helpers.PentairControllerLightMode;
import org.openhab.binding.pentair.internal.handler.helpers.PentairControllerSchedule;
import org.openhab.binding.pentair.internal.handler.helpers.PentairControllerStatus;
import org.openhab.binding.pentair.internal.handler.helpers.PentairHeatStatus;
import org.openhab.binding.pentair.internal.parser.PentairBasePacket;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.openhab.binding.pentair.internal.utils.ExpiringCache;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelGroupUID;
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 PentairControllerHandler} is responsible for implementation of the EasyTouch Controller. It will handle
* commands sent to a thing and implements the different channels. It also parses of the packets seen on the
* bus from the controller.
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
public class PentairControllerHandler extends PentairBaseThingHandler {
private static final int NUM_CIRCUITS = PentairControllerStatus.NUMCIRCUITS;
private static final int NUM_SCHEDULES = 9;
private static final int CACHE_EXPIRY = (int) TimeUnit.SECONDS.toMillis(60);
private static final int CACHE_EXPIRY_LONG = (int) TimeUnit.MINUTES.toMillis(30);
private static final List<String> CIRCUIT_GROUPS = List.of(GROUP_CONTROLLER_SPACIRCUIT,
GROUP_CONTROLLER_AUX1CIRCUIT, GROUP_CONTROLLER_AUX2CIRCUIT, GROUP_CONTROLLER_AUX3CIRCUIT,
GROUP_CONTROLLER_AUX4CIRCUIT, GROUP_CONTROLLER_POOLCIRCUIT, GROUP_CONTROLLER_AUX5CIRCUIT,
GROUP_CONTROLLER_AUX6CIRCUIT, GROUP_CONTROLLER_AUX7CIRCUIT, GROUP_CONTROLLER_AUX8CIRCUIT,
GROUP_CONTROLLER_FEATURE1, GROUP_CONTROLLER_FEATURE2, GROUP_CONTROLLER_FEATURE3, GROUP_CONTROLLER_FEATURE4,
GROUP_CONTROLLER_FEATURE5, GROUP_CONTROLLER_FEATURE6, GROUP_CONTROLLER_FEATURE7, GROUP_CONTROLLER_FEATURE8);
private List<ChannelUID> circuitSwitchUIDs = new ArrayList<ChannelUID>();
private boolean serviceMode = false;
private Unit<Temperature> uom = SIUnits.CELSIUS;
private final Logger logger = LoggerFactory.getLogger(PentairControllerHandler.class);
private @Nullable ScheduledFuture<?> syncTimeJob;
private long lastScheduleTypeWrite;
private final ExpiringCache<PentairControllerStatus> controllerStatusCache = new ExpiringCache<>(CACHE_EXPIRY);
private final ExpiringCache<PentairHeatStatus> heatStatusCache = new ExpiringCache<>(CACHE_EXPIRY);
private int majorrev, minorrev;
private PentairControllerActions actions = new PentairControllerActions();
@SuppressWarnings("unchecked")
private final ExpiringCache<PentairControllerCircuit>[] circuitsCache = new ExpiringCache[NUM_CIRCUITS];
@SuppressWarnings("unchecked")
private final ExpiringCache<PentairControllerSchedule>[] schedulesCache = new ExpiringCache[NUM_SCHEDULES];
private @Nullable PentairControllerLightMode lightMode;
public PentairControllerHandler(Thing thing) {
super(thing);
for (int i = 0; i < NUM_SCHEDULES; i++) {
schedulesCache[i] = new ExpiringCache<PentairControllerSchedule>(CACHE_EXPIRY_LONG);
}
for (int i = 0; i < NUM_CIRCUITS; i++) {
circuitsCache[i] = new ExpiringCache<PentairControllerCircuit>(CACHE_EXPIRY_LONG);
}
}
@Override
public void initialize() {
for (String group : CIRCUIT_GROUPS) {
circuitSwitchUIDs.add(new ChannelUID(new ChannelGroupUID(this.getThing().getUID(), group),
CHANNEL_CONTROLLER_CIRCUITSWITCH));
}
super.initialize();
}
@Override
public void goOnline() {
// Only a single controller is supported on the Pentair bus so prevent multiple controller
// things being created.
PentairBaseBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) { // will not be null here since this is validated in initialize of the super
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.bridge-missing");
return;
}
PentairControllerHandler handler = bridgeHandler.findController();
if (handler != null && !handler.equals(this)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.duplicate-controller");
} else {
super.goOnline();
}
}
@Override
public void finishOnline() {
super.finishOnline();
actions.initialize(Objects.requireNonNull(getBridgeHandler()).getBaseActions(), getPentairID());
// setup syncTimeJob to run once a day. The initial syncTime is called as part of the initControllerSettings as
// part of the controller coming online
syncTimeJob = scheduler.scheduleWithFixedDelay(this::syncTime, 1, 1, TimeUnit.DAYS);
scheduler.execute(() -> initControllerSettings());
}
public void syncTime() {
boolean synctime = ((boolean) getConfig().get(CONTROLLER_CONFIGSYNCTIME));
if (synctime) {
logger.debug("Synchronizing System Time with Pentair controller");
Calendar now = Calendar.getInstance();
actions.setClockSettings(now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE),
now.get(Calendar.DAY_OF_WEEK), now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.MONTH) + 1,
now.get(Calendar.YEAR) - 2000);
}
}
public void initControllerSettings() {
int i;
actions.getSWVersion();
actions.getHeatStatus();
actions.getClockSettings();
for (i = 1; i <= NUM_CIRCUITS; i++) {
actions.getCircuitNameFunction(i);
}
for (i = 1; i <= NUM_SCHEDULES; i++) {
actions.getSchedule(i);
}
actions.getLightGroups();
actions.getValves();
syncTime();
}
@Override
public void goOffline(ThingStatusDetail detail) {
super.goOffline(detail);
ScheduledFuture<?> syncTimeJob = this.syncTimeJob;
if (syncTimeJob != null) {
syncTimeJob.cancel(true);
}
}
public @Nullable PentairControllerCircuit getCircuitByGroupID(String group) {
int index = CIRCUIT_GROUPS.indexOf(group);
if (index == -1) {
return null;
}
return circuitsCache[index].getLastKnownValue();
}
public int getScheduleNumber(String name) {
int scheduleNum;
scheduleNum = Integer.parseInt(name.substring(GROUP_CONTROLLER_SCHEDULE.length()));
if (scheduleNum < 1 || scheduleNum > NUM_SCHEDULES) {
return 0;
}
return scheduleNum;
}
public @Nullable PentairControllerSchedule getScheduleByGroupID(String groupid) {
int scheduleNumber = getScheduleNumber(groupid);
if (scheduleNumber == 0) {
return null;
}
PentairControllerSchedule schedule = schedulesCache[scheduleNumber - 1].getLastKnownValue();
return schedule;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String group = channelUID.getGroupId();
if (group == null) {
return;
}
if (command instanceof RefreshType) {
logger.debug("handleCommand (refresh): {}", channelUID.getId());
switch (group) {
case GROUP_CONTROLLER_POOLHEAT:
case GROUP_CONTROLLER_SPAHEAT:
handleRefreshHeatStatusChannel(channelUID);
return;
case GROUP_CONTROLLER_STATUS:
handleRefreshStatusChannel(channelUID);
return;
case GROUP_CONTROLLER_POOLCIRCUIT:
case GROUP_CONTROLLER_SPACIRCUIT:
case GROUP_CONTROLLER_AUX1CIRCUIT:
case GROUP_CONTROLLER_AUX2CIRCUIT:
case GROUP_CONTROLLER_AUX3CIRCUIT:
case GROUP_CONTROLLER_AUX4CIRCUIT:
case GROUP_CONTROLLER_AUX5CIRCUIT:
case GROUP_CONTROLLER_AUX6CIRCUIT:
case GROUP_CONTROLLER_AUX7CIRCUIT:
case GROUP_CONTROLLER_AUX8CIRCUIT:
case GROUP_CONTROLLER_FEATURE1:
case GROUP_CONTROLLER_FEATURE2:
case GROUP_CONTROLLER_FEATURE3:
case GROUP_CONTROLLER_FEATURE4:
case GROUP_CONTROLLER_FEATURE5:
case GROUP_CONTROLLER_FEATURE6:
case GROUP_CONTROLLER_FEATURE7:
case GROUP_CONTROLLER_FEATURE8:
handleRefreshCircuitChannel(channelUID);
return;
}
if (group.substring(0, GROUP_CONTROLLER_SCHEDULE.length()).equals(GROUP_CONTROLLER_SCHEDULE)) {
handleRefreshScheduleChannel(channelUID);
return;
}
return;
}
logger.debug("handleCommand: {}", channelUID.getId());
switch (channelUID.getIdWithoutGroup()) {
case CHANNEL_CONTROLLER_CIRCUITSWITCH: {
if (!(command instanceof OnOffType onOffCommand)) {
logger.trace("Command is not OnOffType");
break;
}
int index = CIRCUIT_GROUPS.indexOf(group);
if (index == -1) {
break;
}
boolean state = onOffCommand == OnOffType.ON;
actions.setCircuitSwitch(index + 1, state);
break;
}
case CHANNEL_CONTROLLER_LIGHTMODE: {
if (!(command instanceof StringType)) {
break;
}
String str = command.toString();
PentairControllerLightMode lightMode;
try {
lightMode = PentairControllerLightMode.valueOf(str);
actions.setLightMode(lightMode);
} catch (IllegalArgumentException e) {
logger.debug("Invalid light mode: {}", str);
}
break;
}
case CHANNEL_CONTROLLER_SCHEDULESTRING: {
if (!(command instanceof StringType)) {
break;
}
PentairControllerSchedule schedule = getScheduleByGroupID(group);
if (schedule == null) {
break;
}
String str = command.toString();
if (!schedule.fromString(str)) {
logger.debug("schedule invalid format: {}", str);
}
break;
}
case CHANNEL_CONTROLLER_SCHEDULETYPE: {
if (!(command instanceof StringType)) {
break;
}
PentairControllerSchedule schedule = getScheduleByGroupID(group);
if (schedule == null) {
break;
}
String str = command.toString();
// In order to prevent accidental programming of schedules by an inadvertent update, make sure the same
// value is written twice to this field within 5s. Only then will the schedule update command be
// sent to the controller.
boolean bUpdate = (str.equals(schedule.getScheduleTypeStr())
&& ((System.currentTimeMillis() - lastScheduleTypeWrite) < 5000) && schedule.isDirty());
if (!schedule.setScheduleType(str)) {
return;
}
lastScheduleTypeWrite = System.currentTimeMillis();
if (bUpdate) {
actions.saveSchedule(schedule);
lastScheduleTypeWrite = 0;
refreshGroupChannels(group);
}
break;
}
case CHANNEL_CONTROLLER_SCHEDULESTART: {
if (!(command instanceof Number numberCommand)) {
break;
}
PentairControllerSchedule schedule = getScheduleByGroupID(group);
if (schedule == null) {
break;
}
int start = numberCommand.intValue();
schedule.setScheduleStart(start);
break;
}
case CHANNEL_CONTROLLER_SCHEDULEEND: {
if (!(command instanceof Number numberCommand)) {
break;
}
PentairControllerSchedule schedule = getScheduleByGroupID(group);
if (schedule == null) {
break;
}
int end = numberCommand.intValue();
schedule.setScheduleEnd(end);
break;
}
case CHANNEL_CONTROLLER_SCHEDULECIRCUIT: {
if (!(command instanceof Number numberCommand)) {
break;
}
PentairControllerSchedule schedule = getScheduleByGroupID(group);
if (schedule == null) {
break;
}
int circuit = numberCommand.intValue();
schedule.setScheduleCircuit(circuit);
break;
}
case CHANNEL_CONTROLLER_SCHEDULEDAYS: {
if (!(command instanceof StringType)) {
break;
}
PentairControllerSchedule schedule = getScheduleByGroupID(group);
if (schedule == null) {
break;
}
String days = command.toString();
schedule.setDays(days);
break;
}
case CHANNEL_CONTROLLER_SETPOINT: {
if (!(command instanceof QuantityType<?>)) {
break;
}
PentairHeatStatus heatStatus = heatStatusCache.getLastKnownValue();
if (heatStatus == null) {
return;
}
@SuppressWarnings("unchecked")
QuantityType<Temperature> newTempQT = (QuantityType<Temperature>) command;
newTempQT = newTempQT.toUnit(uom); // convert to units for the controller
if (newTempQT == null) {
return;
}
int newTemp = newTempQT.intValue();
switch (group) {
case GROUP_CONTROLLER_SPAHEAT:
heatStatus.spaSetPoint = newTemp;
break;
case GROUP_CONTROLLER_POOLHEAT:
heatStatus.poolSetPoint = newTemp;
break;
}
actions.setHeatStatus(heatStatus);
break;
}
case CHANNEL_CONTROLLER_HEATERDELAY: {
if (!(command instanceof OnOffType onOffCommand)) {
break;
}
if (onOffCommand != OnOffType.OFF) { // Delay can only be cancelled
break;
}
actions.cancelDelay();
}
}
}
@Override
public void processPacketFrom(PentairBasePacket packet) {
PentairStandardPacket p = (PentairStandardPacket) packet;
switch (p.getByte(PentairStandardPacket.ACTION)) {
case 0x01: // Ack
logger.trace("[{}] Ack command from device: {}", p.getSource(), p);
break;
case 0x02: // Controller Status
if (p.getPacketLengthHeader() != 29) {
logger.debug("Expected length of 29: {}", p);
return;
}
logger.trace("[{}] Controller Status: {}", p.getSource(), p);
int preambleByte = p.getByte(PentairStandardPacket.PREAMBLE); // Adjust what byte is used for preamble
actions.setPreambleByte(preambleByte);
if (waitStatusForOnline) {
finishOnline();
}
PentairControllerStatus currentControllerStatus = controllerStatusCache.getLastKnownValue();
PentairControllerStatus newControllerStatus = new PentairControllerStatus();
newControllerStatus.parsePacket(p);
// always update the cached value to reset the expire timer
controllerStatusCache.putValue(newControllerStatus);
// Refresh initially when currentControllerStatus is not set - or when status has changed
if (currentControllerStatus == null || !newControllerStatus.equals(currentControllerStatus)) {
logger.debug("[{}] New controller status: {} - {}", p.getSource(), newControllerStatus, p);
this.uom = newControllerStatus.uom;
this.serviceMode = newControllerStatus.serviceMode;
refreshChannelsFromUIDs(circuitSwitchUIDs);
refreshGroupChannels(GROUP_CONTROLLER_STATUS);
handleRefreshHeatStatusChannel(new ChannelUID(this.getThing().getUID(), GROUP_CONTROLLER_POOLHEAT,
CHANNEL_CONTROLLER_TEMPERATURE));
handleRefreshHeatStatusChannel(new ChannelUID(this.getThing().getUID(), GROUP_CONTROLLER_SPAHEAT,
CHANNEL_CONTROLLER_TEMPERATURE));
}
break;
case 0x04: // Pump control panel on/off - handled in intelliflo controller
// Controller sends packet often to keep control of the motor
int data = p.getPacketLengthHeader() > 5 ? p.getByte(PentairStandardPacket.STARTOFDATA) & 0xFF : -1;
logger.debug("[{}] Pump control panel on/off: {}|{}|{} - {}", p.getSource(),
p.getByte(PentairStandardPacket.ACTION), //
p.getByte(PentairStandardPacket.LENGTH), data, p);
break;
case 0x05: // Current Clock - A5 01 0F 10 05 08 0E 09 02 1D 04 11 00 00 - H M DOW D M YY YY ??
int hour = p.getByte(0 + PentairStandardPacket.STARTOFDATA);
int minute = p.getByte(1 + PentairStandardPacket.STARTOFDATA);
int dow = p.getByte(2 + PentairStandardPacket.STARTOFDATA);
int day = p.getByte(3 + PentairStandardPacket.STARTOFDATA);
int month = p.getByte(4 + PentairStandardPacket.STARTOFDATA);
int year = p.getByte(5 + PentairStandardPacket.STARTOFDATA);
logger.debug("[{}] System Clock: {}.{}.{} {}:{}, DOW={}", p.getSource(), day, month, year, hour, minute,
dow);
break;
case 0x06: // Set run mode
// No action - have not verified these commands, here for documentation purposes and future enhancement
if (p.getPacketLengthHeader() != 1) {
logger.debug("[{}] Expected run mode length of 1: {}", p.getSource(), p);
return;
}
int run = p.getByte(PentairStandardPacket.STARTOFDATA) & 0xFF;
String s;
switch (run) {
case 0x04: // off
s = "OFF";
break;
case 0x0A: // on
s = "ON";
break;
default:
s = "n/a (" + run + ")";
}
logger.debug("[{}] Set run mode for device {}: {} ", p.getSource(), p.getDest(), s);
break;
case 0x07: // Pump Status - handled in IntelliFlo handler
logger.trace("[{}] Pump request status (unseen): {}", p.getSource(), p);
break;
case 0x08: // Heat Status - A5 01 0F 10 08 0D 4B 4B 4D 55 5E 07 00 00 58 00 00 00 
if (p.getPacketLengthHeader() != 0x0D) {
logger.debug("Expected length of 13: {}", p);
return;
}
PentairHeatStatus heatStatus = new PentairHeatStatus(p);
heatStatusCache.putValue(heatStatus);
logger.debug("[{}] Heat status: {} - {}", p.getSource(), heatStatus, p);
refreshGroupChannels(GROUP_CONTROLLER_POOLHEAT);
refreshGroupChannels(GROUP_CONTROLLER_SPAHEAT);
break;
case 0x0A: // Custom Names
logger.trace("[{}] Get Custom Names (unseen): {}", p.getSource(), p);
break;
case 0x0B: // Circuit Names
int index;
index = p.getByte(0 + PentairStandardPacket.STARTOFDATA);
index--; // zero index
if (index < 0 || index >= NUM_CIRCUITS) {
break;
}
PentairControllerCircuit circuit = new PentairControllerCircuit(index + 1);
circuit.setName(p.getByte(2 + PentairStandardPacket.STARTOFDATA));
circuit.setFunction(p.getByte(1 + PentairStandardPacket.STARTOFDATA));
circuitsCache[index].putValue(circuit);
refreshGroupChannels(CIRCUIT_GROUPS.get(index));
logger.debug("[{}] Circuit Names - Circuit: {}, Function: {}, Name: {}", p.getSource(), circuit.id,
circuit.circuitFunction.getFriendlyName(), circuit.circuitName.getFriendlyName());
break;
case 0x11: // schedule - A5 1E 0F 10 11 07 01 06 0B 00 0F 00 7F
PentairControllerSchedule schedule = new PentairControllerSchedule(p);
if (schedule.id < 1 || schedule.id > NUM_SCHEDULES) {
break;
}
String groupID = schedule.getGroupID();
schedulesCache[schedule.id - 1].putValue(schedule);
refreshGroupChannels(groupID);
logger.debug(
"[{}] Controller Schedule - ID: {}, Name: {}, Type: {}, Circuit: {}, Start Time: {}:{}, End Time: {}:{}, Days: {}",
p.getSource(), schedule.id, schedule.type.getName(), schedule.type, schedule.circuit,
schedule.start / 60, schedule.start % 60, schedule.end / 60, schedule.end % 60, schedule.days);
break;
case 0x12: // IntelliChem
logger.debug("[{}] IntelliChem status: {}", p.getSource(), p);
break;
case 0x19: // Intellichlor status
logger.trace("[{}] Intellichlor status: {}", p.getSource(), p);
break;
case 0x1B: // Pump config (Extended)
logger.debug("[{}] Pump Config: {}", p.getSource(), p);
break;
case 0x1D: // Valves
logger.debug("[{}] Values: {}", p.getSource(), p);
break;
case 0x1E: // High speed circuits
logger.debug("[{}] High speed circuits: {}", p.getSource(), p);
break;
case 0x20: // spa-side is4/is10 remote
case 0x21: // spa-side quicktouch remotes
logger.debug("[{}] Spa-side remotes: {}", p.getSource(), p);
break;
case 0x22: // Solar/Heat Pump status
logger.trace("[{}] Solar/Heat Pump status: {}", p.getSource(), p);
break;
case 0x23: // Delay status
logger.debug("[{}] Delay status: {}", p.getSource(), p);
break;
case 0x27: // Light Groups/Positions
logger.trace("[{}] Light Groups/Positions; {}", p.getSource(), p);
break;
case 0x28: // Settings? heat mode
logger.trace("[{}] Settings?: {}", p.getSource(), p);
break;
case 0x60: // set intellibrite colors
logger.trace("[{}] Set intellibrite colors: {}", p.getSource(), p);
break;
case 0x86: // Set Curcuit On/Off
logger.trace("[{}] Set Circuit Function On/Off (unseen): {}", p.getSource(), p);
break;
case 0xD2: // Get Intellichem status
logger.trace("[{}] Get IntelliChem status: {}", p.getSource(), p);
break;
case 0xFC: // Status - A5 1E 0F 10 FC 11 00 02 0A 00 00 01 0A 00 00 00 00 00 00 00 00 00 00
majorrev = p.getByte(1 + PentairStandardPacket.STARTOFDATA);
minorrev = p.getByte(2 + PentairStandardPacket.STARTOFDATA);
String version = String.format("%d.%d", majorrev, minorrev);
updateProperty(PROPERTY_CONTROLLER_FIRMWAREVERSION, version);
logger.debug("[{}] SW Version - {}", p.getSource(), version);
break;
default:
logger.debug("[{}] Not Implemented {}: {}", p.getSource(), p.getByte(PentairStandardPacket.ACTION), p);
break;
}
}
/*
* Helper routines to handle Refresh commands
*/
private void handleRefreshScheduleChannel(ChannelUID channelUID) {
String group = channelUID.getGroupId();
String channel = channelUID.getIdWithoutGroup();
if (group == null) {
return;
}
PentairControllerSchedule schedule = getScheduleByGroupID(group);
if (schedule == null) {
return;
}
switch (channel) {
case CHANNEL_CONTROLLER_SCHEDULESTRING:
updateChannel(channelUID, schedule.toString());
return;
case CHANNEL_CONTROLLER_SCHEDULETYPE:
String type = schedule.getScheduleTypeStr();
updateChannel(channelUID, type);
return;
case CHANNEL_CONTROLLER_SCHEDULECIRCUIT:
updateChannel(channelUID, schedule.circuit);
return;
case CHANNEL_CONTROLLER_SCHEDULESTART:
updateChannel(channelUID, schedule.start, Units.MINUTE);
return;
case CHANNEL_CONTROLLER_SCHEDULEEND:
updateChannel(channelUID, schedule.end, Units.MINUTE);
return;
case CHANNEL_CONTROLLER_SCHEDULEDAYS:
updateChannel(channelUID, schedule.getDays());
return;
}
}
private void handleRefreshStatusChannel(ChannelUID channelUID) {
String channel = channelUID.getIdWithoutGroup();
PentairControllerStatus status = controllerStatusCache.getValue(() -> {
actions.getStatus();
});
if (status == null) {
return;
}
switch (channel) {
case CHANNEL_CONTROLLER_AIRTEMPERATURE:
updateChannel(channelUID, status.airTemp, uom);
return;
case CHANNEL_CONTROLLER_SOLARTEMPERATURE:
updateChannel(channelUID, status.solarTemp, uom);
return;
case CHANNEL_CONTROLLER_SERVICEMODE:
updateChannel(channelUID, status.serviceMode);
return;
case CHANNEL_CONTROLLER_SOLARON:
updateChannel(channelUID, status.solarOn);
return;
case CHANNEL_CONTROLLER_HEATERON:
updateChannel(channelUID, status.heaterOn);
return;
case CHANNEL_CONTROLLER_HEATERDELAY:
updateChannel(channelUID, status.heaterDelay);
return;
case CHANNEL_CONTROLLER_LIGHTMODE:
PentairControllerLightMode lightMode = this.lightMode;
if (lightMode != null) {
updateChannel(channelUID, lightMode.name());
}
return;
}
}
private void handleRefreshHeatStatusChannel(ChannelUID channelUID) {
String group = channelUID.getGroupId();
String channel = channelUID.getIdWithoutGroup();
if (group == null) {
return;
}
boolean poolChannel = group.equals(GROUP_CONTROLLER_POOLHEAT);
PentairHeatStatus heatStatus = heatStatusCache.getLastKnownValue();
if (heatStatus == null) {
return;
}
switch (channel) {
case CHANNEL_CONTROLLER_SETPOINT:
updateChannel(channelUID, (poolChannel) ? heatStatus.poolSetPoint : heatStatus.spaSetPoint, uom);
return;
case CHANNEL_CONTROLLER_HEATMODE:
updateChannel(channelUID,
(poolChannel) ? heatStatus.poolHeatMode.name() : heatStatus.spaHeatMode.name());
return;
case CHANNEL_CONTROLLER_TEMPERATURE: {
PentairControllerStatus status = this.controllerStatusCache.getLastKnownValue();
if (status == null) {
return;
}
if (poolChannel) {
if (status.pool) {
updateChannel(channelUID, status.poolTemp, uom);
} else {
updateState(channelUID, UnDefType.UNDEF);
}
} else {
if (status.spa) {
updateChannel(channelUID, status.poolTemp, uom);
} else {
updateState(channelUID, UnDefType.UNDEF);
}
}
}
}
}
private void handleRefreshCircuitChannel(ChannelUID channelUID) {
String group = channelUID.getGroupId();
String channel = channelUID.getIdWithoutGroup();
if (group == null) {
return;
}
switch (channel) {
case CHANNEL_CONTROLLER_CIRCUITNAME:
case CHANNEL_CONTROLLER_CIRCUITFUNCTION: {
PentairControllerCircuit circuit = getCircuitByGroupID(group);
if (circuit == null) {
return;
}
String circuitString = channel.equals(CHANNEL_CONTROLLER_CIRCUITNAME)
? circuit.circuitName.getFriendlyName()
: circuit.circuitFunction.getFriendlyName();
updateChannel(channelUID, circuitString);
return;
}
case CHANNEL_CONTROLLER_CIRCUITSWITCH: {
PentairControllerStatus status = controllerStatusCache.getValue(() -> {
actions.getStatus();
});
int index = CIRCUIT_GROUPS.indexOf(group);
if (index == -1 || status == null) {
return;
}
boolean on = status.circuits[index];
updateChannel(channelUID, on);
return;
}
}
}
public boolean getServiceMode() {
return serviceMode;
}
}

View File

@ -1,498 +0,0 @@
/**
* Copyright (c) 2010-2024 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 java.util.Objects;
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 onOffCommand) {
boolean state = onOffCommand == 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 decimalCommand) {
int sp = decimalCommand.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, OnOffType.from((p29cur.pool)));
}
break;
case EASYTOUCH_SPA:
if (p29 == null || (p29.spa != p29cur.spa)) {
updateState(channel, OnOffType.from((p29cur.spa)));
}
break;
case EASYTOUCH_AUX1:
if (p29 == null || (p29.aux1 != p29cur.aux1)) {
updateState(channel, OnOffType.from((p29cur.aux1)));
}
break;
case EASYTOUCH_AUX2:
if (p29 == null || (p29.aux2 != p29cur.aux2)) {
updateState(channel, OnOffType.from((p29cur.aux2)));
}
break;
case EASYTOUCH_AUX3:
if (p29 == null || (p29.aux3 != p29cur.aux3)) {
updateState(channel, OnOffType.from((p29cur.aux3)));
}
break;
case EASYTOUCH_AUX4:
if (p29 == null || (p29.aux4 != p29cur.aux4)) {
updateState(channel, OnOffType.from((p29cur.aux4)));
}
break;
case EASYTOUCH_AUX5:
if (p29 == null || (p29.aux5 != p29cur.aux5)) {
updateState(channel, OnOffType.from((p29cur.aux5)));
}
break;
case EASYTOUCH_AUX6:
if (p29 == null || (p29.aux6 != p29cur.aux6)) {
updateState(channel, OnOffType.from((p29cur.aux6)));
}
break;
case EASYTOUCH_AUX7:
if (p29 == null || (p29.aux7 != p29cur.aux7)) {
updateState(channel, OnOffType.from((p29cur.aux7)));
}
break;
case EASYTOUCH_FEATURE1:
if (p29 == null || (p29.feature1 != p29cur.feature1)) {
updateState(channel, OnOffType.from((p29cur.feature1)));
}
break;
case EASYTOUCH_FEATURE2:
if (p29 == null || (p29.feature2 != p29cur.feature2)) {
updateState(channel, OnOffType.from((p29cur.feature2)));
}
break;
case EASYTOUCH_FEATURE3:
if (p29 == null || (p29.feature3 != p29cur.feature3)) {
updateState(channel, OnOffType.from((p29cur.feature3)));
}
break;
case EASYTOUCH_FEATURE4:
if (p29 == null || (p29.feature4 != p29cur.feature4)) {
updateState(channel, OnOffType.from((p29cur.feature4)));
}
break;
case EASYTOUCH_FEATURE5:
if (p29 == null || (p29.feature5 != p29cur.feature5)) {
updateState(channel, OnOffType.from((p29cur.feature5)));
}
break;
case EASYTOUCH_FEATURE6:
if (p29 == null || (p29.feature6 != p29cur.feature6)) {
updateState(channel, OnOffType.from((p29cur.feature6)));
}
break;
case EASYTOUCH_FEATURE7:
if (p29 == null || (p29.feature7 != p29cur.feature7)) {
updateState(channel, OnOffType.from((p29cur.feature7)));
}
break;
case EASYTOUCH_FEATURE8:
if (p29 == null || (p29.feature8 != p29cur.feature8)) {
updateState(channel, OnOffType.from((p29cur.feature8)));
}
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 || (!Objects.equals(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 || (!Objects.equals(phsp.poolheatmodestr, phspcur.poolheatmodestr))) {
if (phspcur.poolheatmodestr != null) {
updateState(channel, new StringType(phspcur.poolheatmodestr));
}
}
break;
case EASYTOUCH_HEATACTIVE:
if (p29 == null || (p29.heatactive != p29cur.heatactive)) {
updateState(channel, new DecimalType(p29cur.heatactive));
}
break;
case EASYTOUCH_POOLSETPOINT:
if (phsp == null || (phsp.poolsetpoint != phspcur.poolsetpoint)) {
updateState(channel, new DecimalType(phspcur.poolsetpoint));
}
break;
case EASYTOUCH_SPASETPOINT:
if (phsp == null || (phsp.spasetpoint != phspcur.spasetpoint)) {
updateState(channel, new DecimalType(phspcur.spasetpoint));
}
break;
case DIAG:
if (p29 == null || (p29.diag != p29cur.diag)) {
updateState(channel, new DecimalType(p29cur.diag));
}
break;
}
}
}

View File

@ -12,12 +12,14 @@
*/
package org.openhab.binding.pentair.internal.handler;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pentair.internal.config.PentairIPBridgeConfig;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
@ -26,91 +28,76 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler for the IPBridge. Implements the connect and disconnect abstract methods of {@link PentairBaseBridgeHandler}
* The {@link PentairIPBridgeHandler } class implements the the IPBridge.
* Implements the connect and disconnect abstract methods of {@link PentairBaseBridgeHandler}
*
* @author Jeff James - Initial contribution
*
*/
@NonNullByDefault
public class PentairIPBridgeHandler extends PentairBaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(PentairIPBridgeHandler.class);
/** Socket object for connection */
protected Socket socket;
public PentairIPBridgeConfig config = new PentairIPBridgeConfig();
private @Nullable Socket socket;
public PentairIPBridgeHandler(Bridge bridge) {
super(bridge);
}
@Override
protected synchronized void connect() {
PentairIPBridgeConfig configuration = getConfigAs(PentairIPBridgeConfig.class);
id = configuration.id;
protected synchronized boolean connect() {
config = getConfigAs(PentairIPBridgeConfig.class);
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);
this.socket = new Socket(config.address, config.port);
Socket socket = this.socket;
if (socket == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.communication-error.ip-stream-error");
return false;
}
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
if (inputStream == null || outputStream == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.communication-error.ip-stream-error");
return false;
}
setInputStream(socket.getInputStream());
setOutputStream(socket.getOutputStream());
logger.debug("Pentair IPBridge connected to {}:{}", config.address, config.port);
} catch (UnknownHostException e) {
String msg = String.format("unknown host name: %s", configuration.address);
if (getThing().getStatus() != ThingStatus.OFFLINE) {
String msg = String.format("unknown host name: %s, %s", config.address, e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
return;
}
return false;
} catch (IOException e) {
String msg = String.format("cannot open connection to %s", configuration.address);
if (getThing().getStatus() != ThingStatus.OFFLINE) {
String msg = String.format("cannot open connection to %s, %s", config.address, e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
return;
}
return false;
}
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");
}
return true;
}
@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;
}
Socket socket = this.socket;
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
logger.error("error when closing socket ", e);
logger.debug("error when closing socket ", e);
}
socket = null;
}

View File

@ -0,0 +1,167 @@
/**
* Copyright (c) 2010-2024 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.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pentair.internal.handler.helpers.PentairIntelliChem;
import org.openhab.binding.pentair.internal.parser.PentairBasePacket;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairIntelliChemHandler} is responsible for implementation of the IntelliChem. This will
* parse of status packets to set the stat for various channels. All channels are read only.
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
public class PentairIntelliChemHandler extends PentairBaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(PentairIntelliChemHandler.class);
private PentairIntelliChem pic = new PentairIntelliChem();
private String firmwareVersion = "";
public PentairIntelliChemHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
// The IntelliChem routinely updates the state, so just refresh to last state
switch (channelUID.getId()) {
case CHANNEL_INTELLICHEM_PHREADING:
updateChannel(channelUID, pic.phReading);
break;
case CHANNEL_INTELLICHEM_ORPREADING:
updateChannel(channelUID, pic.orpReading);
break;
case CHANNEL_INTELLICHEM_PHSETPOINT:
updateChannel(channelUID, pic.phSetPoint);
break;
case CHANNEL_INTELLICHEM_ORPSETPOINT:
updateChannel(channelUID, pic.orpSetPoint);
break;
case CHANNEL_INTELLICHEM_TANK1LEVEL:
updateChannel(channelUID, pic.tank1Level);
break;
case CHANNEL_INTELLICHEM_TANK2LEVEL:
updateChannel(channelUID, pic.tank2Level);
break;
case CHANNEL_INTELLICHEM_CALCIUMHARDNESS:
updateChannel(channelUID, pic.calciumHardness, Units.PARTS_PER_MILLION);
break;
case CHANNEL_INTELLICHEM_CYAREADING:
updateChannel(channelUID, pic.cyaReading);
break;
case CHANNEL_INTELLICHEM_ALKALINITY:
updateChannel(channelUID, pic.alkalinity);
break;
case CHANNEL_INTELLICHEM_LSI:
updateChannel(channelUID, pic.lsi);
break;
case CHANNEL_INTELLICHEM_PHDOSERTYPE:
updateChannel(channelUID, pic.phDoserType.name());
break;
case CHANNEL_INTELLICHEM_ORPDOSERTYPE:
updateChannel(channelUID, pic.orpDoserType.name());
break;
case CHANNEL_INTELLICHEM_PHDOSERSTATUS:
updateChannel(channelUID, pic.phDoserStatus.name());
break;
case CHANNEL_INTELLICHEM_ORPDOSERSTATUS:
updateChannel(channelUID, pic.orpDoserStatus.name());
break;
case CHANNEL_INTELLICHEM_PHDOSETIME:
updateChannel(channelUID, pic.phDoseTime, Units.SECOND);
break;
case CHANNEL_INTELLICHEM_ORPDOSETIME:
updateChannel(channelUID, pic.orpDoseTime, Units.SECOND);
break;
case CHANNEL_INTELLICHEM_SALTLEVEL:
updateChannel(channelUID, pic.saltLevel);
break;
case CHANNEL_INTELLICHEM_ALARMWATERFLOW:
updateChannel(channelUID, pic.alarmWaterFlow);
break;
case CHANNEL_INTELLICHEM_ALARMPH:
updateChannel(channelUID, pic.alarmPh);
break;
case CHANNEL_INTELLICHEM_ALARMORP:
updateChannel(channelUID, pic.alarmOrp);
break;
case CHANNEL_INTELLICHEM_ALARMPHTANK:
updateChannel(channelUID, pic.alarmPhTank);
break;
case CHANNEL_INTELLICHEM_ALARMORPTANK:
updateChannel(channelUID, pic.alarmOrpTank);
break;
case CHANNEL_INTELLICHEM_ALARMPROBEFAULT:
updateChannel(channelUID, pic.alarmProbeFault);
break;
case CHANNEL_INTELLICHEM_WARNINGPHLOCKOUT:
updateChannel(channelUID, pic.warningPhLockout);
break;
case CHANNEL_INTELLICHEM_WARNINGPHDAILYLIMITREACHED:
updateChannel(channelUID, pic.warningPhDailyLimitReached);
break;
case CHANNEL_INTELLICHEM_WARNINGORPDAILYLIMITREACHED:
updateChannel(channelUID, pic.warningOrpDailyLimitReached);
break;
case CHANNEL_INTELLICHEM_WARNINGINVALIDSETUP:
updateChannel(channelUID, pic.warningInvalidSetup);
break;
case CHANNEL_INTELLICHEM_WARNINGCHLORINATORCOMMERROR:
updateChannel(channelUID, pic.warningChlorinatorCommError);
break;
}
}
}
@Override
public void processPacketFrom(PentairBasePacket packet) {
if (waitStatusForOnline) {
finishOnline();
}
PentairStandardPacket p = (PentairStandardPacket) packet;
switch (p.getByte(PentairStandardPacket.ACTION)) {
case 0x12: // Status packet
pic.parsePacket(p);
logger.debug("Intellichem status: {}: ", pic.toString());
this.refreshAllChannels();
if (!this.firmwareVersion.equals(pic.firmwareVersion)) {
firmwareVersion = pic.firmwareVersion;
updateProperty(PROPERTY_INTELLICHEM_FIRMWAREVERSION, pic.firmwareVersion);
}
break;
default:
logger.debug("Unhandled Intellichem packet: {}", p.toString());
break;
}
}
}

View File

@ -14,13 +14,17 @@ 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 java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pentair.internal.parser.PentairBasePacket;
import org.openhab.binding.pentair.internal.parser.PentairIntelliChlorPacket;
import org.openhab.core.library.unit.Units;
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.slf4j.Logger;
@ -28,96 +32,149 @@ 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.
* 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
*/
@NonNullByDefault
public class PentairIntelliChlorHandler extends PentairBaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(PentairIntelliChlorHandler.class);
protected PentairPacketIntellichlor pic3cur = new PentairPacketIntellichlor();
protected PentairPacketIntellichlor pic4cur = new PentairPacketIntellichlor();
public int version;
public String name = "";
/** for a saltoutput packet, represents the salt output percent */
private int saltOutput;
/** for a salinity packet, is value of salinity. Must be multiplied by 50 to get the actual salinity value. */
private int salinity;
private boolean ok;
private boolean lowFlow;
private boolean lowSalt;
private boolean veryLowSalt;
private boolean highCurrent;
private boolean cleanCell;
private boolean lowVoltage;
private boolean lowWaterTemp;
private boolean commError;
public PentairIntelliChlorHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
logger.debug("Initializing IntelliChlor - Thing ID: {}.", this.getThing().getUID());
public void goOnline() {
PentairIntelliChlorHandler handler = Objects.requireNonNull(getBridgeHandler()).findIntellichlor();
id = 0; // Intellichlor doesn't have ID
updateStatus(ThingStatus.ONLINE);
if (handler != null && !handler.equals(this)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.duplicate-intllichlor");
return;
} else {
super.goOnline();
}
}
@Override
public void dispose() {
logger.debug("Thing {} disposed.", getThing().getUID());
public void goOffline(ThingStatusDetail detail) {
super.goOffline(detail);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
logger.debug("IntelliChlor received refresh command");
updateChannel(channelUID.getId(), null);
logger.trace("IntelliChlor received refresh command");
switch (channelUID.getId()) {
case CHANNEL_INTELLICHLOR_SALTOUTPUT:
updateChannel(channelUID, saltOutput, Units.PERCENT);
break;
case CHANNEL_INTELLICHLOR_SALINITY:
updateChannel(channelUID, salinity, Units.PARTS_PER_MILLION);
break;
case CHANNEL_INTELLICHLOR_OK:
updateChannel(channelUID, ok);
break;
case CHANNEL_INTELLICHLOR_LOWFLOW:
updateChannel(channelUID, lowFlow);
break;
case CHANNEL_INTELLICHLOR_LOWSALT:
updateChannel(channelUID, lowSalt);
break;
case CHANNEL_INTELLICHLOR_VERYLOWSALT:
updateChannel(channelUID, veryLowSalt);
break;
case CHANNEL_INTELLICHLOR_HIGHCURRENT:
updateChannel(channelUID, highCurrent);
break;
case CHANNEL_INTELLICHLOR_CLEANCELL:
updateChannel(channelUID, cleanCell);
break;
case CHANNEL_INTELLICHLOR_LOWVOLTAGE:
updateChannel(channelUID, lowVoltage);
break;
case CHANNEL_INTELLICHLOR_LOWWATERTEMP:
updateChannel(channelUID, lowWaterTemp);
break;
case CHANNEL_INTELLICHLOR_COMMERROR:
updateChannel(channelUID, commError);
break;
}
}
}
@Override
public void processPacketFrom(PentairPacket p) {
PentairPacketIntellichlor pic = (PentairPacketIntellichlor) p;
public void processPacketFrom(PentairBasePacket packet) {
PentairIntelliChlorPacket p = (PentairIntelliChlorPacket) packet;
switch (pic.getLength()) {
case 3:
if (pic.getCmd() != 0x11) { // only packets with 0x11 have valid saltoutput numbers.
switch (p.getByte(PentairIntelliChlorPacket.ACTION)) {
case 0x03:
version = p.getVersion();
name = p.getName();
Map<String, String> editProperties = editProperties();
editProperties.put(CHANNEL_INTELLICHLOR_PROPERTYVERSION, Integer.toString(version));
editProperties.put(CHANNEL_INTELLICHLOR_PROPERTYMODEL, name);
updateProperties(editProperties);
logger.debug("Intellichlor version: {}, {}", version, name);
break;
case 0x11: // set salt output % command
saltOutput = p.getSaltOutput();
updateChannel(new ChannelUID(getThing().getUID(), CHANNEL_INTELLICHLOR_SALTOUTPUT), saltOutput,
Units.PERCENT);
logger.debug("Intellichlor set output % {}", saltOutput);
break;
case 0x12: // response to set salt output
if (waitStatusForOnline) { // Only go online after first response from the Intellichlor
finishOnline();
}
PentairPacketIntellichlor pic3Old = pic3cur;
pic3cur = pic;
salinity = p.getSalinity();
updateChannel(INTELLICHLOR_SALTOUTPUT, pic3Old);
ok = p.getOk();
lowFlow = p.getLowFlow();
lowSalt = p.getLowSalt();
veryLowSalt = p.getVeryLowSalt();
highCurrent = p.getHighCurrent();
cleanCell = p.getCleanCell();
lowVoltage = p.getLowVoltage();
lowWaterTemp = p.getLowWaterTemp();
break;
case 4:
if (pic.getCmd() != 0x12) {
break;
}
this.refreshAllChannels();
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));
if (logger.isDebugEnabled()) {
String status = String.format(
"saltoutput = %d, salinity = %d, ok = %b, lowflow = %b, lowsalt = %b, verylowsalt = %b, highcurrent = %b, cleancell = %b, lowvoltage = %b, lowwatertemp = %b",
saltOutput, salinity, ok, lowFlow, lowSalt, veryLowSalt, highCurrent, cleanCell, lowVoltage,
lowWaterTemp);
logger.debug("IntelliChlor salinity/status: {}, {}", salinity, status);
}
break;
case INTELLICHLOR_SALTOUTPUT:
if (pic == null || (pic.saltoutput != pic3cur.saltoutput)) {
updateState(channel, new DecimalType(pic3cur.saltoutput));
}
case 0x14:
logger.debug("IntelliChlor GetModel request (0x14): {}", p.toString());
break;
}
}

View File

@ -14,16 +14,26 @@ package org.openhab.binding.pentair.internal.handler;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.pentair.internal.PentairBindingConstants;
import org.openhab.binding.pentair.internal.PentairPacket;
import org.openhab.binding.pentair.internal.PentairPacketPumpStatus;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pentair.internal.actions.PentairIntelliFloActions;
import org.openhab.binding.pentair.internal.handler.helpers.PentairPumpStatus;
import org.openhab.binding.pentair.internal.parser.PentairBasePacket;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.Units;
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.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
@ -31,158 +41,226 @@ 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.
* parse status packets to set the stat for various channels.
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
public class PentairIntelliFloHandler extends PentairBaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(PentairIntelliFloHandler.class);
protected PentairPacketPumpStatus ppscur = new PentairPacketPumpStatus();
private PentairPumpStatus pumpStatus = new PentairPumpStatus();
// runmode is used to send watchdog to pump when running
private boolean runMode = false;
private static @Nullable ScheduledFuture<?> pollingJob;
private PentairIntelliFloActions actions = new PentairIntelliFloActions();
public PentairIntelliFloHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
logger.debug("Initializing Intelliflo - Thing ID: {}.", this.getThing().getUID());
public void finishOnline() {
super.finishOnline();
actions.initialize(Objects.requireNonNull(getBridgeHandler()).getBaseActions(), getPentairID());
id = ((BigDecimal) getConfig().get("id")).intValue();
updateStatus(ThingStatus.ONLINE);
startPollingJob();
}
@Override
public void dispose() {
logger.debug("Thing {} disposed.", getThing().getUID());
public void goOffline(ThingStatusDetail detail) {
super.goOffline(detail);
// PentairIntelliFloHandler.pollingJob will be cancelled when called and there are no pumps associated
// with the bridge
}
public PentairIntelliFloActions getActions() {
return actions;
}
public void setRunMode(boolean runMode) {
this.runMode = runMode;
}
private void startPollingJob() {
if (pollingJob == null) {
PentairIntelliFloHandler.pollingJob = scheduler
.scheduleWithFixedDelay(PentairIntelliFloHandler::pumpWatchDog, 10, 30, TimeUnit.SECONDS);
}
}
private static void stopPollingJob() {
ScheduledFuture<?> pollingJob = PentairIntelliFloHandler.pollingJob;
if (pollingJob != null) {
pollingJob.cancel(true);
}
PentairIntelliFloHandler.pollingJob = null;
}
/**
* 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.
*
*/
private static void pumpWatchDog() {
boolean pumpsStillOnline = false;
Bridge bridge = PentairBaseBridgeHandler.getSingleBridge();
if (bridge == null) {
PentairIntelliFloHandler.stopPollingJob();
return;
}
Collection<Thing> things = bridge.getThings();
for (Thing t : things) {
if (!t.getThingTypeUID().equals(INTELLIFLO_THING_TYPE)) {
continue;
}
if (t.getStatus() != ThingStatus.ONLINE) {
continue;
}
pumpsStillOnline = true;
PentairIntelliFloHandler handler = (PentairIntelliFloHandler) t.getHandler();
if (handler == null) {
continue;
}
if (handler.runMode) {
handler.getActions().coreSetOnOROff(true);
} else {
handler.getActions().getStatus();
}
}
if (!pumpsStillOnline) {
PentairIntelliFloHandler.stopPollingJob();
}
}
// checkOtherMaster - check to make sure the system does not have a controller OR that the controller is in
// servicemode
private boolean checkOtherMaster() {
PentairBaseBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.bridge-missing");
return true;
}
PentairControllerHandler handler = bridgeHandler.findController();
return (handler != null && !handler.getServiceMode());
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
logger.debug("IntelliFlo received refresh command");
updateChannel(channelUID.getId(), null);
if (command instanceof OnOffType onOffCommand) {
boolean state = onOffCommand == OnOffType.ON;
switch (channelUID.getId()) {
case CHANNEL_INTELLIFLO_RUN:
case CHANNEL_INTELLIFLO_RPM:
if (!state) {
updateState(INTELLIFLO_RUNPROGRAM, OnOffType.OFF);
}
actions.setOnOrOff(state);
break;
case INTELLIFLO_RUNPROGRAM:
if (checkOtherMaster()) {
logger.debug("Unable to send command to pump as there is another master in the system");
return;
}
if (command instanceof DecimalType programNumber) {
if (programNumber.intValue() == 0) {
actions.setOnOrOff(false);
} else {
actions.setRunProgram(programNumber.intValue());
}
}
}
} else if (command instanceof DecimalType decimalCommand) {
int num = decimalCommand.intValue();
switch (channelUID.getId()) {
case CHANNEL_INTELLIFLO_RPM:
updateState(INTELLIFLO_RUNPROGRAM, OnOffType.OFF);
actions.setRPM(num);
break;
}
} else if (command instanceof RefreshType) {
switch (channelUID.getId()) {
case CHANNEL_INTELLIFLO_RUN:
updateChannel(channelUID, pumpStatus.run);
break;
case CHANNEL_INTELLIFLO_POWER:
updateChannel(channelUID, pumpStatus.power, Units.WATT);
break;
case CHANNEL_INTELLIFLO_RPM:
updateChannel(channelUID, pumpStatus.rpm);
break;
case INTELLIFLO_GPM:
updateChannel(channelUID, pumpStatus.gpm, ImperialUnits.GALLON_PER_MINUTE);
break;
case INTELLIFLO_STATUS1:
updateChannel(channelUID, pumpStatus.status1);
break;
case INTELLIFLO_STATUS2:
updateChannel(channelUID, pumpStatus.status2);
break;
}
}
}
@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);
public void processPacketFrom(PentairBasePacket packet) {
if (waitStatusForOnline) {
finishOnline();
}
PentairStandardPacket p = (PentairStandardPacket) packet;
switch (p.getByte(PentairStandardPacket.ACTION)) {
case 0x01: // Pump command - A5 00 10 60 01 02 00 20
logger.debug("[{}] Pump command (ack)", p.getSource());
break;
case 4: // Pump control panel on/off
logger.trace("Turn pump control panel (ack) {}: {} - {}", p.getSource(),
p.getByte(PentairPacket.STARTOFDATA), p);
case 0x04: // Pump control panel on/off
boolean remotemode;
remotemode = p.getByte(0 + PentairStandardPacket.STARTOFDATA) == (byte) 0xFF;
logger.debug("[{}] Pump control panel (ack): {}", p.getSource(), remotemode);
break;
case 5: // Set pump mode
logger.trace("Set pump mode (ack) {}: {} - {}", p.getSource(), p.getByte(PentairPacket.STARTOFDATA), p);
case 0x05: // Set pump mode ack
logger.debug("[{}] Set pump mode (ack): {}", p.getSource(),
p.getByte(0 + PentairStandardPacket.STARTOFDATA));
break;
case 6: // Set run mode
logger.trace("Set run mode (ack) {}: {} - {}", p.getSource(), p.getByte(PentairPacket.STARTOFDATA), p);
case 0x06: // Set run mode ack
logger.debug("[{}] Set run mode (ack): {}", p.getSource(),
p.getByte(0 + PentairStandardPacket.STARTOFDATA));
break;
case 7: // Pump status (after a request)
if (p.getLength() != 15) {
logger.debug("Expected length of 15: {}", p);
case 0x07: // Pump status (after a request)
if (p.getPacketLengthHeader() != 15) {
logger.debug("[{}]: Expected length of 15 onm pump status: {}", p.getSource(), 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);
pumpStatus.parsePacket(p);
logger.debug("[{}] Pump status: {}", p.getSource(), pumpStatus);
this.refreshAllChannels();
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, OnOffType.from((ppscur.run)));
}
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));
}
logger.debug("[{}] Unhandled Intelliflo command {}: {}", p.getSource(), p.getAction(), p.toString());
break;
}
}

View File

@ -12,130 +12,115 @@
*/
package org.openhab.binding.pentair.internal.handler;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pentair.internal.config.PentairSerialBridgeConfig;
import org.openhab.core.io.transport.serial.PortInUseException;
import org.openhab.core.io.transport.serial.SerialPort;
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
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}
* The {@link PentairSerialBridgeHandler } implments the class for the serial bridge. Implements the connect and
* disconnect abstract methods of {@link PentairBaseBridgeHandler}
*
* @author Jeff James - initial contribution
*
*/
@NonNullByDefault
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 PentairSerialBridgeConfig config = new PentairSerialBridgeConfig();
public PentairSerialBridgeHandler(Bridge bridge) {
private final SerialPortManager serialPortManager;
@Nullable
private SerialPort port;
@Nullable
private SerialPortIdentifier portIdentifier;
public PentairSerialBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
super(bridge);
this.serialPortManager = serialPortManager;
}
@Override
protected synchronized void connect() {
PentairSerialBridgeConfig configuration = getConfigAs(PentairSerialBridgeConfig.class);
protected synchronized boolean connect() {
config = getConfigAs(PentairSerialBridgeConfig.class);
if (config.serialPort.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.serial-port-empty");
return false;
}
this.portIdentifier = serialPortManager.getIdentifier(config.serialPort);
SerialPortIdentifier portIdentifier = this.portIdentifier;
if (portIdentifier == null) {
if (getThing().getStatus() != ThingStatus.OFFLINE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.communication-error.serial-port-not-found" + config.serialPort);
}
return false;
}
try {
CommPortIdentifier ci = CommPortIdentifier.getPortIdentifier(configuration.serialPort);
CommPort cp = ci.open("openhabpentairbridge", 10000);
if (cp == null) {
throw new IllegalStateException("cannot open serial port!");
logger.trace("connect port: {}", config.serialPort);
if (portIdentifier.isCurrentlyOwned()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.communication-error.serial-port-busy" + config.serialPort);
return false;
}
if (cp instanceof SerialPort serialPort) {
port = serialPort;
} else {
throw new IllegalStateException("unknown port type");
this.port = portIdentifier.open("org.openhab.binding.pentair", 10000);
SerialPort port = this.port;
if (port == null) {
return false;
}
port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
port.disableReceiveFraming();
port.disableReceiveThreshold();
port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
reader = new BufferedInputStream(port.getInputStream());
writer = new BufferedOutputStream(port.getOutputStream());
logger.info("Pentair Bridge connected to serial port: {}", configuration.serialPort);
InputStream is = port.getInputStream();
OutputStream os = port.getOutputStream();
if (is != null) {
setInputStream(is);
}
if (os != null) {
setOutputStream(os);
}
} 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;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.communication-error.serial-port-busy" + config.serialPort);
return false;
} catch (UnsupportedCommOperationException | IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.communication-error.serial-port-error" + config.serialPort + ", " + e.getMessage());
return false;
}
parser = new Parser();
thread = new Thread(parser);
thread.start();
logger.debug("Pentair Bridge connected to serial port: {}", config.serialPort);
if (port != null && reader != null && writer != null) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Unable to connect");
}
return true;
}
@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;
}
SerialPort port = this.port;
if (port != null) {
port.close();
port = null;

View File

@ -0,0 +1,228 @@
/**
* Copyright (c) 2010-2024 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.helpers;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PentairControllerCircuit } class is used to define circuit/features of the controller
*
* @author Jeff James - initial contribution
*
*/
@NonNullByDefault
public class PentairControllerCircuit {
public enum CircuitName {
EMPTY(-1, ""),
NOTUSED(0, "NOT USED"),
AERATOR(1, "AERATOR"),
AIRBLOWER(2, "AIR BLOWER"),
AUX1(3, "AUX 1"),
AUX2(4, "AUX 2"),
AUX3(5, "AUX 3"),
AUX4(6, "AUX 4"),
AUX5(7, "AUX 5"),
AUX6(8, "AUX 6"),
AUX7(9, "AUX 7"),
AUX8(10, "AUX 8"),
AUX9(11, "AUX 9"),
AUX10(12, "AUX 10"),
BACKWASH(13, "BACKWASH"),
BACKLIGHT(14, "BACK LIGHT"),
BBQLIGHT(15, "BBQ LIGHT"),
BEACHLIGHT(16, "BEACH LIGHT"),
BOOSTERPUMP(17, "BOOSTER PUMP"),
BUGLIGHT(18, "BUG LIGHT"),
CABANALTS(19, "CABANA LTS"),
CHEMFEEDER(20, "CHEM. 2FEEDER"),
CHLORINATOR(21, "CHLORINATOR"),
CLEANER(22, "CLEANER"),
COLORWHEEL(23, "COLOR WHEEL"),
DECKLIGHT(24, "DECK LIGHT"),
DRAINLINE(25, "DRAIN LINE"),
DRIVELIGHT(26, "DRIVE LIGHT"),
EDGEPUMP(27, "EDGE PUMP"),
ENTRYLIGHT(28, "ENTRY LIGHT"),
FAN(29, "FAN"),
FIBEROPTIC(30, "FIBER OPTIC"),
FIBERWORKS(31, "FIBER WORKS"),
FILLLINE(32, "FILL LINE"),
FLOORCLNR(33, "FLOOR CLNR"),
FOGGER(34, "FOGGER"),
FOUNTAIN(35, "FOUNTAIN"),
FOUNTAIN1(36, "FOUNTAIN 1"),
FOUNTAIN2(37, "FOUNTAIN 2"),
FOUNTAIN3(38, "FOUNTAIN 3"),
FOUNTAINS(39, "FOUNTAINS"),
FRONTLIGHT(40, "FRONT LIGHT"),
GARDENLTS(41, "GARDEN LTS"),
GAZEBOLTS(42, "GAZEBO LTS"),
HIGHSPEED(43, "HIGH SPEED"),
HITEMP(44, "HI-TEMP"),
HOUSELIGHT(45, "HOUSE LIGHT"),
JETS(46, "JETS"),
LIGHTS(47, "LIGHTS"),
LOWSPEED(48, "LOW SPEED"),
LOTEMP(49, "LO-TEMP"),
MALIBULTS(50, "MALIBU LTS"),
MIST(51, "MIST"),
MUSIC(52, "MUSIC"),
NOTUSED2(53, "NOT USED"),
OZONATOR(54, "OZONATOR"),
PATHLIGHTS(55, "PATH LIGHTS"),
PATIOLTS(56, "PATIO LTS"),
PERIMETERL(57, "PERIMETER L"),
PG2000(58, "PG2000"),
PONDLIGHT(59, "POND LIGHT"),
POOLPUMP(60, "POOL PUMP"),
POOL(61, "POOL"),
POOLHIGH(62, "POOL HIGH"),
POOLLIGHT(63, "POOL LIGHT"),
POOLLOW(64, "POOL LOW"),
SAM(65, "SAM"),
POOLSAM1(66, "POOL SAM 1"),
POOLSAM2(67, "POOL SAM 2"),
POOLSAM3(68, "POOL SAM 3"),
SECURITYLT(69, "SECURITY LT"),
SLIDE(70, "SLIDE"),
SOLAR(71, "SOLAR"),
SPA(72, "SPA"),
SPAHIGH(73, "SPA HIGH"),
SPALIGHT(74, "SPA LIGHT"),
SPALOW(75, "SPA LOW"),
SPASAL(76, "SPA SAL"),
SPASAM(77, "SPA SAM"),
SPAWTRFLL(78, "SPA WTRFLL"),
SPILLWAY(79, "SPILLWAY"),
SPRINKLERS(80, "SPRINKLERS"),
STREAM(81, "STREAM"),
STAUTELT(82, "STATUE LT"),
SWIMJETS(83, "SWIM JETS"),
WTRFEATURE(84, "WTR FEATURE"),
WTRFEATLT(85, "WTR FEAT LT"),
WATERFALL(86, "WATERFALL"),
WATERFALL1(87, "WATERFALL 1"),
WATERFALL2(88, "WATERFALL 2"),
WATERFALL3(89, "WATERFALL 3"),
WHIRLPOOL(90, "WHIRLPOOL"),
WTRFLLGHT(91, "WTRFL LGHT"),
YARDLIGHT(92, "YARD LIGHT"),
AUXEXTRA(93, "AUX EXTRA"),
FEATURE1(94, "FEATURE 1"),
FEATURE2(95, "FEATURE 2"),
FEATURE3(96, "FEATURE 3"),
FEATURE4(97, "FEATURE 4"),
FEATURE5(98, "FEATURE 5"),
FEATURE6(99, "FEATURE 6"),
FEATURE7(100, "FEATURE 7"),
FEATURE8(101, "FEATURE 8"),
USERNAME01(200, "USERNAME-01"),
USERNAME02(201, "USERNAME-02"),
USERNAME03(202, "USERNAME-03"),
USERNAME04(203, "USERNAME-04"),
USERNAME05(204, "USERNAME-05"),
USERNAME06(205, "USERNAME-06"),
USERNAME07(206, "USERNAME-07"),
USERNAME08(207, "USERNAME-08"),
USERNAME09(208, "USERNAME-09"),
USERNAME10(209, "USERNAME-10");
private final int number;
private final String friendlyName;
CircuitName(int n, String friendlyName) {
this.number = n;
this.friendlyName = friendlyName;
}
public int getCode() {
return number;
}
public String getFriendlyName() {
return friendlyName;
}
public static CircuitName valueOfModeNumber(int number) {
return Arrays.stream(values()).filter(value -> (value.getCode() == number)).findFirst()
.orElse(CircuitName.EMPTY);
}
}
public enum CircuitFunction {
EMPTY(-1, ""),
GENERIC(0, "GENERIC"),
SPA(1, "SPA"),
POOL(2, "POOL"),
MASTERCLEANER(5, "MASTER CLEANER"),
LIGHT(7, "LIGHT"),
SAMLIGHT(9, "SAM LIGHT"),
SALLIGHT(10, "SAL LIGHT"),
PHOTONGEN(11, "PHOTON GEN"),
COLORWHEEL(12, "COLOR WHEEL"),
VALVES(13, "VALVES"),
SPILLWAY(14, "SPILLWAY"),
FLOORCLEANER(15, "FLOOR CLEANER"),
INTELLIBRITE(16, "INTELLIBRITE"),
MAGICSTREAM(17, "MAGICSTREAM"),
NOTUSED(19, "NOT USED"),
FREEZEPROTECT(64, "FREEZE PROTECTION ON");
private final int code;
private final String friendlyName;
private CircuitFunction(int code, String friendlyName) {
this.code = code;
this.friendlyName = friendlyName;
}
public int getCode() {
return code;
}
public String getFriendlyName() {
return friendlyName;
}
public static CircuitFunction valueOfModeNumber(int number) {
return Arrays.stream(values()).filter(value -> (value.getCode() == number)).findFirst()
.orElse(CircuitFunction.EMPTY);
}
}
public final int id;
public CircuitName circuitName = CircuitName.EMPTY;
public CircuitFunction circuitFunction = CircuitFunction.EMPTY;
public PentairControllerCircuit(int id) {
this.id = id;
}
public void setName(int n) {
circuitName = CircuitName.valueOfModeNumber(n);
}
public void setName(CircuitName circuitName) {
this.circuitName = circuitName;
}
public void setFunction(int f) {
circuitFunction = CircuitFunction.valueOfModeNumber(f);
}
public void setFunction(CircuitFunction circuitFunction) {
this.circuitFunction = circuitFunction;
}
}

View File

@ -0,0 +1,63 @@
/**
* Copyright (c) 2010-2024 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.helpers;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PentairControllerLightMode } enum constants used to define the different light modes of the controller.
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
public enum PentairControllerLightMode {
EMPTY(-1, ""),
OFF(0, "Off"),
ON(1, "On"),
COLORSYNC(128, "Color Sync"),
COLORSWIM(144, "Color Swim"),
COLORSET(160, "COLORSET"),
PARTY(177, "PARTY"),
ROMANCE(178, "ROMANCE"),
CARIBBENA(179, "CARIBBEAN"),
AMERICAN(180, "AMERICAN"),
SUNSET(181, "SUNSET"),
ROYAL(182, "ROYAL"),
BLUE(193, "BLUE"),
GREEN(194, "GREEN"),
RED(195, "RED"),
WHITE(96, "WHITE"),
MAGENTA(197, "MAGENTA");
private final int number;
private final String name;
private PentairControllerLightMode(int n, String name) {
this.number = n;
this.name = name;
}
public int getModeNumber() {
return number;
}
public String getName() {
return name;
}
public static PentairControllerLightMode valueOfModeNumber(int modeNumber) {
return Arrays.stream(values()).filter(value -> (value.getModeNumber() == modeNumber)).findFirst().orElse(EMPTY);
}
}

View File

@ -0,0 +1,303 @@
/**
* Copyright (c) 2010-2024 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.helpers;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.GROUP_CONTROLLER_SCHEDULE;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pentair.internal.actions.PentairControllerActions;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
/**
* The {@link PentairControllerSchdule } class stores the schedule details for a given controller schedule.
*
* @author Jeff James - initial contribution
*
*/
@NonNullByDefault
public class PentairControllerSchedule {
public static final int ID = 0 + +PentairStandardPacket.STARTOFDATA;
private static final int CIRCUIT = 1 + PentairStandardPacket.STARTOFDATA;
private static final int STARTH = 2 + PentairStandardPacket.STARTOFDATA;
private static final int STARTM = 3 + PentairStandardPacket.STARTOFDATA;
private static final int ENDH = 4 + PentairStandardPacket.STARTOFDATA;
private static final int ENDM = 5 + PentairStandardPacket.STARTOFDATA;
private static final int DAYS = 6 + PentairStandardPacket.STARTOFDATA;
private static final String REGEX_SCHEDULE = "^(NONE|NORMAL|EGGTIMER|ONCEONLY),(\\\\d+),(\\\\d+):(\\\\d+),(\\\\d+):(\\\\d+),([SMTWRFY]+)";
private static final Pattern PATTERN_SCHEDULE = Pattern.compile(REGEX_SCHEDULE);
private boolean dirty;
public enum ScheduleType {
NONE("None"),
NORMAL("Normal"),
EGGTIMER("Egg Timer"),
ONCEONLY("Once Only"),
UNKNOWN("Unknown");
private final String name;
private ScheduleType(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public int id;
public int circuit;
public ScheduleType type = ScheduleType.UNKNOWN;
public int start;
public int end;
public int days;
public PentairControllerSchedule() {
super();
}
public PentairControllerSchedule(PentairStandardPacket p) {
super();
parsePacket(p);
}
public boolean isDirty() {
return dirty;
}
public void setDirty(boolean d) {
this.dirty = d;
}
public void parsePacket(PentairStandardPacket p) {
this.id = p.getByte(ID);
this.circuit = p.getByte(CIRCUIT);
this.days = p.getByte(DAYS);
if (p.getByte(STARTH) == 25) {
this.type = ScheduleType.EGGTIMER;
this.start = 0;
this.end = p.getByte(ENDH) * 60 + p.getByte(ENDM);
} else if (p.getByte(ENDH) == 26) {
this.type = ScheduleType.ONCEONLY;
this.start = p.getByte(STARTH) * 60 + p.getByte(STARTM);
this.end = 0;
} else if (circuit == 0) {
this.type = ScheduleType.NONE;
this.start = 0;
this.end = 0;
} else {
this.type = ScheduleType.NORMAL;
this.start = p.getByte(STARTH) * 60 + p.getByte(STARTM);
this.end = p.getByte(ENDH) * 60 + p.getByte(ENDM);
}
}
public String getScheduleTypeStr() {
return type.name();
}
public boolean setScheduleCircuit(int c) {
if (circuit == c) {
return true;
}
if (c > 18 || c <= 0) {
return false;
}
this.circuit = c;
this.dirty = true;
return true;
}
public boolean setScheduleStart(int min) {
if (min == start) {
return true;
}
if (min > 1440 || min < 0) {
return false;
}
this.start = min;
this.dirty = true;
return true;
}
public boolean setScheduleEnd(int min) {
if (min == end) {
return true;
}
if (min > 1440 || min < 0) {
return false;
}
this.end = min;
this.dirty = true;
return true;
}
public boolean setScheduleType(ScheduleType type) {
if (this.type == type) {
return true;
}
this.type = type;
this.dirty = true;
return true;
}
public boolean setScheduleType(String typestring) {
ScheduleType scheduleType;
try {
scheduleType = ScheduleType.valueOf(typestring);
} catch (IllegalArgumentException e) {
return false;
}
return setScheduleType(scheduleType);
}
public boolean setDays(String d) {
final String dow = "SMTWRFY";
days = 0;
for (int i = 0; i <= 6; i++) {
if (d.indexOf(dow.charAt(i)) >= 0) {
days |= 1 << i;
}
}
dirty = true;
return true;
}
public @Nullable PentairStandardPacket getWritePacket(int controllerid, int preamble) {
byte[] packet = { (byte) 0xA5, (byte) preamble, (byte) controllerid, (byte) 0x00 /* source */,
(byte) PentairControllerActions.ControllerCommand.SAVE_SCHEDULE.send, (byte) 7, (byte) id,
(byte) circuit, (byte) (start / 60), (byte) (start % 60), (byte) (end / 60), (byte) (end % 60),
(byte) days };
PentairStandardPacket p = new PentairStandardPacket(packet);
switch (type) {
case NONE:
p.setByte(STARTH, (byte) 0);
p.setByte(STARTM, (byte) 0);
p.setByte(ENDH, (byte) 0);
p.setByte(ENDM, (byte) 0);
p.setByte(CIRCUIT, (byte) 0);
p.setByte(DAYS, (byte) 0);
break;
case NORMAL:
break;
case ONCEONLY:
p.setByte(ENDH, (byte) 26);
p.setByte(ENDM, (byte) 0);
break;
case EGGTIMER:
p.setByte(STARTH, (byte) 25);
p.setByte(STARTM, (byte) 0);
p.setByte(DAYS, (byte) 0);
break;
case UNKNOWN:
return null;
}
return p;
}
public String getDays() {
final String dow = "SMTWRFY";
String str = "";
for (int i = 0; i <= 6; i++) {
if ((((days >> i) & 0x01)) == 0x01) {
str += dow.charAt(i);
}
}
return str;
}
@Override
public String toString() {
String str = String.format("%s,%d,%02d:%02d,%02d:%02d,%s", getScheduleTypeStr(), circuit, start / 60,
start % 60, end / 60, end % 60, getDays());
return str;
}
public boolean fromString(String str) {
String schedulestr = str.toUpperCase();
Matcher m = PATTERN_SCHEDULE.matcher(schedulestr);
if (!m.find()) {
return false;
}
if (!setScheduleCircuit(Integer.parseUnsignedInt(m.group(2)))) {
return false;
}
int min = Integer.parseUnsignedInt(m.group(3)) * 60 + Integer.parseUnsignedInt(m.group(4));
if (!setScheduleStart(min)) {
return false;
}
min = Integer.parseUnsignedInt(m.group(5)) * 60 + Integer.parseUnsignedInt(m.group(6));
if (!setScheduleEnd(min)) {
return false;
}
if (!setDays(m.group(7))) {
return false;
}
ScheduleType t;
try {
t = ScheduleType.valueOf(m.group(1));
} catch (IllegalArgumentException e) {
return false;
}
if (!setScheduleType(t)) {
return false;
}
dirty = true;
return true;
}
public String getGroupID() {
String groupID = GROUP_CONTROLLER_SCHEDULE + Integer.toString(id);
return groupID;
}
}

View File

@ -0,0 +1,146 @@
/**
* Copyright (c) 2010-2024 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.helpers;
import java.util.Arrays;
import java.util.Objects;
import javax.measure.Unit;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairControllerStatus } class contain all status values from the controller.
*
* @author Jeff James - initial contribution
*
*/
@NonNullByDefault
public class PentairControllerStatus { // 29 byte packet format
private final Logger logger = LoggerFactory.getLogger(PentairControllerStatus.class);
public static final int NUMCIRCUITS = 18;
private static final int HOUR = 0 + PentairStandardPacket.STARTOFDATA;
private static final int MIN = 1 + PentairStandardPacket.STARTOFDATA;
private static final int EQUIP1 = 2 + PentairStandardPacket.STARTOFDATA;
private static final int EQUIP2 = 3 + PentairStandardPacket.STARTOFDATA;
private static final int EQUIP3 = 4 + PentairStandardPacket.STARTOFDATA;
private static final int STATUS = 9 + PentairStandardPacket.STARTOFDATA; // Celsius (0x04) or Farenheit, Service
// Mode (0x01)
private static final int HEAT_ACTIVE = 10 + PentairStandardPacket.STARTOFDATA;
private static final int HEATER_DELAY = 12 + PentairStandardPacket.STARTOFDATA; // Something to do with heat?
private static final int POOL_TEMP = 14 + PentairStandardPacket.STARTOFDATA;
private static final int SPA_TEMP = 15 + PentairStandardPacket.STARTOFDATA;
private static final int AIR_TEMP = 18 + PentairStandardPacket.STARTOFDATA;
private static final int SOLAR_TEMP = 19 + PentairStandardPacket.STARTOFDATA;
public int hour;
public int min;
/** Individual boolean values representing whether a particular ciruit is on or off */
public int equip;
public boolean pool, spa;
public boolean[] circuits = new boolean[NUMCIRCUITS];
public Unit<Temperature> uom = SIUnits.CELSIUS;
public boolean serviceMode;
public boolean heaterOn;
public boolean solarOn;
public boolean heaterDelay;
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;
/** used to store packet value for reverse engineering, not used in normal operation */
public int diag;
public void parsePacket(PentairStandardPacket p) {
if (p.getPacketLengthHeader() != 29) {
logger.debug("Controller status packet not 29 bytes long");
return;
}
hour = p.getByte(HOUR);
min = p.getByte(MIN);
pool = (p.getByte(EQUIP1) & 0x20) != 0;
spa = (p.getByte(EQUIP1) & 0x01) != 0;
equip = p.getByte(EQUIP3) << 16 | p.getByte(EQUIP2) << 8 | p.getByte(EQUIP1);
for (int i = 0; i < NUMCIRCUITS; i++) {
circuits[i] = ((equip >> i) & 0x0001) == 1;
}
uom = ((p.getByte(STATUS) & 0x04) == 0) ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS;
serviceMode = (p.getByte(STATUS) & 0x01) != 0;
heaterDelay = (p.getByte(HEATER_DELAY) & 0x02) != 0;
diag = p.getByte(HEAT_ACTIVE);
poolTemp = p.getByte(POOL_TEMP);
spaTemp = p.getByte(SPA_TEMP);
airTemp = p.getByte(AIR_TEMP);
solarTemp = p.getByte(SOLAR_TEMP);
solarOn = (p.getByte(HEAT_ACTIVE) & 0x30) != 0;
heaterOn = (p.getByte(HEAT_ACTIVE) & 0x0C) != 0;
}
@Override
public String toString() {
String str = String.format(
"%02d:%02d equip:%s pooltemp:%d spatemp:%d airtemp:%d solarttemp:%d uom:%s, service:%b, heaterDelay:%b",
hour, min, String.format("%18s", Integer.toBinaryString(equip)).replace(' ', '0'), poolTemp, spaTemp,
airTemp, solarTemp, uom.toString(), serviceMode, heaterDelay);
return str;
}
@Override
public boolean equals(@Nullable Object object) {
if (!(object instanceof PentairControllerStatus controllerStatus)) {
return false;
}
PentairControllerStatus p = controllerStatus;
return Arrays.equals(circuits, p.circuits) && poolTemp == p.poolTemp && spaTemp == p.spaTemp
&& airTemp == p.airTemp && solarTemp == p.solarTemp && uom.equals(p.uom) && serviceMode == p.serviceMode
&& solarOn == p.solarOn && heaterOn == p.heaterOn && heaterDelay == p.heaterDelay;
}
@Override
public int hashCode() {
return Objects.hash(circuits, poolTemp, spaTemp, airTemp, solarTemp, uom, serviceMode, solarOn, heaterOn,
heaterDelay);
}
}

View File

@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2024 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.helpers;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
/**
* The {@link PentairHeatStatus } class contain heat set point info. Includes public variables.
*
* @author Jeff James - initial contribution
*
*/
@NonNullByDefault
public class PentairHeatStatus {
public enum HeatMode {
EMPTY(-1, ""),
NONE(0, "None"),
HEATER(1, "Heater"),
SOLARPREFERRED(2, "Solar Preferred"),
SOLAR(3, "Solar");
private final int code;
private final String friendlyName;
private HeatMode(int code, String friendlyName) {
this.code = code;
this.friendlyName = friendlyName;
}
public int getCode() {
return code;
}
public String getFriendlyName() {
return friendlyName;
}
public static HeatMode valueOfCode(int code) {
return Arrays.stream(values()).filter(value -> (value.getCode() == code)).findFirst().orElse(EMPTY);
}
}
@SuppressWarnings("unused")
private static final int POOLTEMP = 1 + PentairStandardPacket.STARTOFDATA;
@SuppressWarnings("unused")
private static final int AIRTEMP = 2 + PentairStandardPacket.STARTOFDATA;
private static final int POOLSETPOINT = 3 + PentairStandardPacket.STARTOFDATA;
private static final int SPASETPOINT = 4 + PentairStandardPacket.STARTOFDATA;
private static final int HEATMODE = 5 + PentairStandardPacket.STARTOFDATA;
@SuppressWarnings("unused")
private static final int SOLARTEMP = 8 + PentairStandardPacket.STARTOFDATA;
public int poolSetPoint;
public HeatMode poolHeatMode = HeatMode.EMPTY;
public int spaSetPoint;
public HeatMode spaHeatMode = HeatMode.EMPTY;
public PentairHeatStatus() {
}
public PentairHeatStatus(PentairStandardPacket p) {
parsePacket(p);
}
public void parsePacket(PentairStandardPacket p) {
poolSetPoint = p.getByte(POOLSETPOINT);
poolHeatMode = HeatMode.valueOfCode(p.getByte(HEATMODE) & 0x03);
spaSetPoint = p.getByte(SPASETPOINT);
spaHeatMode = HeatMode.valueOfCode((p.getByte(HEATMODE) >> 2) & 0x03);
}
@Override
public String toString() {
String str = String.format("poolSetPoint: %d, poolHeatMode: %s, spaSetPoint: %d, spaHeatMode: %s", poolSetPoint,
poolHeatMode.name(), spaSetPoint, spaHeatMode.name());
return str;
}
}

View File

@ -0,0 +1,372 @@
/**
* Copyright (c) 2010-2024 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.helpers;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pentair.internal.parser.PentairBasePacket;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairIntelliChem } class contains key values from the Pentair IntelliChem.
*
* @author Jeff James - initial contribution.
*
*/
@NonNullByDefault
public class PentairIntelliChem {
private final Logger logger = LoggerFactory.getLogger(PentairIntelliChem.class);
private static final int PHREADINGHI = 0 + PentairStandardPacket.STARTOFDATA;
private static final int PHREADINGLO = 1 + PentairStandardPacket.STARTOFDATA;
private static final int ORPREADINGHI = 2 + PentairStandardPacket.STARTOFDATA;
private static final int ORPREADINGLO = 3 + PentairStandardPacket.STARTOFDATA;
private static final int PHSETPOINTHI = 4 + PentairStandardPacket.STARTOFDATA;
private static final int PHSETPOINTLO = 5 + PentairStandardPacket.STARTOFDATA;
private static final int ORPSETPOINTHI = 6 + PentairStandardPacket.STARTOFDATA;
private static final int ORPSETPOINTLO = 7 + PentairStandardPacket.STARTOFDATA;
private static final int PHDOSETIMEHI = 10 + PentairStandardPacket.STARTOFDATA;
private static final int PHDOSETIMELO = 11 + PentairStandardPacket.STARTOFDATA;
private static final int ORPDOSETIMEHI = 14 + PentairStandardPacket.STARTOFDATA;
private static final int ORPDOSETIMELO = 15 + PentairStandardPacket.STARTOFDATA;
@SuppressWarnings("unused")
private static final int PHVOLUMEDOSEDHI = 16 + PentairStandardPacket.STARTOFDATA;
@SuppressWarnings("unused")
private static final int PHVOLUMEDOSEDLO = 17 + PentairStandardPacket.STARTOFDATA;
@SuppressWarnings("unused")
private static final int ORPVOLUMEDOSEDHI = 18 + PentairStandardPacket.STARTOFDATA;
@SuppressWarnings("unused")
private static final int ORPVOLUMEDOSEDLO = 19 + PentairStandardPacket.STARTOFDATA;
private static final int TANK1LEVEL = 20 + PentairStandardPacket.STARTOFDATA;
private static final int TANK2LEVEL = 21 + PentairStandardPacket.STARTOFDATA;
private static final int LSI = 22 + PentairStandardPacket.STARTOFDATA;
private static final int CALCIUMHARDNESSHI = 23 + PentairStandardPacket.STARTOFDATA;
private static final int CALCIUMHARDNESSLO = 24 + PentairStandardPacket.STARTOFDATA;
private static final int CYAREADING = 26 + PentairStandardPacket.STARTOFDATA;
private static final int ALKALINITYHI = 27 + PentairStandardPacket.STARTOFDATA;
private static final int ALKALINITYLO = 28 + PentairStandardPacket.STARTOFDATA;
private static final int SALTLEVEL = 29 + PentairStandardPacket.STARTOFDATA;
@SuppressWarnings("unused")
private static final int TEMPERATURE = 31 + PentairStandardPacket.STARTOFDATA;
private static final int ALARMS = 32 + PentairStandardPacket.STARTOFDATA;
private static final int WARNINGS = 33 + PentairStandardPacket.STARTOFDATA;
private static final int DOSER_TYPE_STATUS = 34 + PentairStandardPacket.STARTOFDATA;
@SuppressWarnings("unused")
private static final int DELAYS = 35 + PentairStandardPacket.STARTOFDATA;
private static final int FIRMWAREMINOR = 36 + PentairStandardPacket.STARTOFDATA;
private static final int FIRMWAREMAJOR = 37 + PentairStandardPacket.STARTOFDATA;
public enum PhDoserType {
NONE,
CO2,
ACID;
private static PhDoserType getType(int num) {
switch (num) {
case 0:
return NONE;
case 1:
return ACID;
case 2:
return CO2;
case 3:
return ACID;
}
return NONE;
}
}
public enum OrpDoserType {
NONE,
ORP;
private static OrpDoserType getType(int num) {
if (num == 0) {
return NONE;
}
return ORP;
}
}
public enum DosingStatus {
NONE,
DOSING,
MIXING,
MONITORING;
private static DosingStatus getType(int num, boolean enabled) {
if (!enabled) {
return NONE;
}
switch (num) {
case 0:
return DOSING;
case 1:
return MIXING;
case 2:
return MONITORING;
}
return NONE;
}
}
public double phReading;
public int orpReading;
public double phSetPoint;
public int orpSetPoint; // Oxidation Reduction Potential
public int tank1Level;
public int tank2Level;
public int calciumHardness;
public int cyaReading; // Cyanuric Acid
public int alkalinity;
public boolean alarmWaterFlow;
public boolean alarmPh;
public boolean alarmOrp;
public boolean alarmPhTank;
public boolean alarmOrpTank;
public boolean alarmProbeFault;
public boolean warningPhLockout;
public boolean warningPhDailyLimitReached;
public boolean warningOrpDailyLimitReached;
public boolean warningInvalidSetup;
public boolean warningChlorinatorCommError;
public double lsi;
public PhDoserType phDoserType = PhDoserType.NONE;
public OrpDoserType orpDoserType = OrpDoserType.NONE;
public DosingStatus phDoserStatus = DosingStatus.NONE;
public DosingStatus orpDoserStatus = DosingStatus.NONE;
public int phDoseTime;
public int orpDoseTime;
public int saltLevel;
public String firmwareVersion = "";
public double calcCalciumHardnessFactor() {
double calciumHardnessFactor = 0;
if (calciumHardness <= 25) {
calciumHardnessFactor = 1.0;
} else if (calciumHardness <= 50) {
calciumHardnessFactor = 1.3;
} else if (calciumHardness <= 75) {
calciumHardnessFactor = 1.5;
} else if (calciumHardness <= 100) {
calciumHardnessFactor = 1.6;
} else if (calciumHardness <= 125) {
calciumHardnessFactor = 1.7;
} else if (calciumHardness <= 150) {
calciumHardnessFactor = 1.8;
} else if (calciumHardness <= 200) {
calciumHardnessFactor = 1.9;
} else if (calciumHardness <= 250) {
calciumHardnessFactor = 2.0;
} else if (calciumHardness <= 300) {
calciumHardnessFactor = 2.1;
} else if (calciumHardness <= 400) {
calciumHardnessFactor = 2.2;
} else if (calciumHardness <= 800) {
calciumHardnessFactor = 2.5;
}
return calciumHardnessFactor;
}
public double calcTemperatureFactor(QuantityType<Temperature> t) {
double temperatureFactor = 0;
int temperature = t.intValue();
if (t.getUnit().equals(SIUnits.CELSIUS)) {
if (temperature <= 0) {
temperatureFactor = 0.0;
} else if (temperature <= 2.8) {
temperatureFactor = 0.1;
} else if (temperature <= 7.8) {
temperatureFactor = 0.2;
} else if (temperature <= 11.7) {
temperatureFactor = 0.3;
} else if (temperature <= 15.6) {
temperatureFactor = 0.4;
} else if (temperature <= 18.9) {
temperatureFactor = 0.5;
} else if (temperature <= 24.4) {
temperatureFactor = 0.6;
} else if (temperature <= 28.9) {
temperatureFactor = 0.7;
} else if (temperature <= 34.4) {
temperatureFactor = 0.8;
} else if (temperature <= 40.6) {
temperatureFactor = 0.9;
}
} else { // Fahrenheit
if (temperature <= 32) {
temperatureFactor = 0.0;
} else if (temperature <= 37) {
temperatureFactor = 0.1;
} else if (temperature <= 46) {
temperatureFactor = 0.2;
} else if (temperature <= 53) {
temperatureFactor = 0.3;
} else if (temperature <= 60) {
temperatureFactor = 0.4;
} else if (temperature <= 66) {
temperatureFactor = 0.5;
} else if (temperature <= 76) {
temperatureFactor = 0.6;
} else if (temperature <= 84) {
temperatureFactor = 0.7;
} else if (temperature <= 94) {
temperatureFactor = 0.8;
} else if (temperature <= 105) {
temperatureFactor = 0.9;
}
}
return temperatureFactor;
}
public double calcCorrectedAlkalinity() {
return alkalinity - cyaReading / 3;
}
public double calcAlkalinityFactor() {
double ppm = calcCorrectedAlkalinity();
double alkalinityFactor = 0;
if (ppm <= 25) {
alkalinityFactor = 1.4;
} else if (ppm <= 50) {
alkalinityFactor = 1.7;
} else if (ppm <= 75) {
alkalinityFactor = 1.9;
} else if (ppm <= 100) {
alkalinityFactor = 2.0;
} else if (ppm <= 125) {
alkalinityFactor = 2.1;
} else if (ppm <= 150) {
alkalinityFactor = 2.2;
} else if (ppm <= 200) {
alkalinityFactor = 2.3;
} else if (ppm <= 250) {
alkalinityFactor = 2.4;
} else if (ppm <= 300) {
alkalinityFactor = 2.5;
} else if (ppm <= 400) {
alkalinityFactor = 2.6;
} else if (ppm <= 800) {
alkalinityFactor = 2.9;
}
return alkalinityFactor;
}
public double calcTotalDisovledSolidsFactor(boolean saltPool) {
// 12.1 for non-salt; 12.2 for salt
if (saltPool) {
return 12.2;
}
return 12.1;
}
public double calcSaturationIndex(@Nullable QuantityType<Temperature> waterTemp, boolean saltPool) {
double alkalinityFactor;
double temperatureFactor = .4; // if no temperature is available, use default value of .4
double saturationIndex;
if (waterTemp != null) {
temperatureFactor = calcTemperatureFactor(waterTemp);
}
alkalinityFactor = calcAlkalinityFactor();
saturationIndex = this.phReading + calcCalciumHardnessFactor() + alkalinityFactor + temperatureFactor
- calcTotalDisovledSolidsFactor(saltPool);
return saturationIndex;
}
/**
* parsePacket - This function will parse a IntelliChem status packet. Note, this is based on the efforts of the
* nodejs-poolController utility since this is not equipment that I have and only minimally tested by the community.
*
* @param p - PentairPacket to parse
*/
public void parsePacket(PentairBasePacket packet) {
PentairStandardPacket p = (PentairStandardPacket) packet;
if (p.getPacketLengthHeader() != 41) {
logger.debug("Intellichem packet not 41 bytes long");
return;
}
phReading = ((p.getByte(PHREADINGHI) << 8) + p.getByte(PHREADINGLO)) / 100.0;
orpReading = (p.getByte(ORPREADINGHI) << 8) + p.getByte(ORPREADINGLO);
phSetPoint = ((p.getByte(PHSETPOINTHI) << 8) + p.getByte(PHSETPOINTLO)) / 100.0;
orpSetPoint = (p.getByte(ORPSETPOINTHI) << 8) + p.getByte(ORPSETPOINTLO);
tank1Level = p.getByte(TANK1LEVEL); // should be value between 1-7
tank2Level = p.getByte(TANK2LEVEL);
calciumHardness = (p.getByte(CALCIUMHARDNESSHI) << 8) + p.getByte(CALCIUMHARDNESSLO);
cyaReading = p.getByte(CYAREADING);
alkalinity = (p.getByte(ALKALINITYHI) << 8) + p.getByte(ALKALINITYLO);
phDoserType = PhDoserType.getType(p.getByte(DOSER_TYPE_STATUS) & 0x03);
orpDoserType = OrpDoserType.getType((p.getByte(DOSER_TYPE_STATUS) & 0x0C) >> 2);
phDoserStatus = DosingStatus.getType((p.getByte(DOSER_TYPE_STATUS) & 0x30) >> 4,
phDoserType != PhDoserType.NONE);
orpDoserStatus = DosingStatus.getType((p.getByte(DOSER_TYPE_STATUS) & 0xC0) >> 6,
orpDoserType != OrpDoserType.NONE);
lsi = ((p.getByte(LSI) & 0x80) != 0) ? (256 - p.getByte(LSI)) / -100.0 : p.getByte(LSI) / 100.0;
phDoseTime = (p.getByte(PHDOSETIMEHI) << 8) + p.getByte(PHDOSETIMELO);
orpDoseTime = (p.getByte(ORPDOSETIMEHI) << 8) + p.getByte(ORPDOSETIMELO);
saltLevel = p.getByte(SALTLEVEL) * 50;
alarmWaterFlow = (p.getByte(ALARMS) & 0x01) != 0;
alarmPh = (p.getByte(ALARMS) & 0x06) != 0;
alarmOrp = (p.getByte(ALARMS) & 0x08) != 0;
alarmPhTank = (p.getByte(ALARMS) & 0x20) != 0;
alarmOrpTank = (p.getByte(ALARMS) & 0x40) != 0;
alarmProbeFault = (p.getByte(ALARMS) & 0x80) != 0;
warningPhLockout = (p.getByte(WARNINGS) & 0x01) != 0;
warningPhDailyLimitReached = (p.getByte(WARNINGS) & 0x02) != 0;
warningOrpDailyLimitReached = (p.getByte(WARNINGS) & 0x04) != 0;
warningInvalidSetup = (p.getByte(WARNINGS) & 0x08) != 0;
warningChlorinatorCommError = (p.getByte(WARNINGS) & 0x10) != 0;
firmwareVersion = String.format("%d.%03d", p.getByte(FIRMWAREMAJOR), p.getByte(FIRMWAREMINOR));
}
@Override
public String toString() {
String str = String.format(
"PH: %.2f, OPR: %d, PH set point: %.2f, ORP set point: %d, tank1: %d, tank2: %d, calcium hardness: %d, cyareading: %d, alkalinity: %d, phDoserType: %s, orpDoserType: %s, phDoserStatus: %b, orpDoserStatus: %b, phDoseTime: %d, orpDoseTime: %d, saturationindex: %f.1",
phReading, orpReading, phSetPoint, orpSetPoint, tank1Level, tank2Level, calciumHardness, cyaReading,
alkalinity, phDoserType.toString(), orpDoserType.toString(), phDoserStatus, orpDoserStatus, phDoseTime,
orpDoseTime, lsi);
return str;
}
}

View File

@ -0,0 +1,102 @@
/**
* Copyright (c) 2010-2024 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.helpers;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairPumpStatus } class contains status fields from the pump status packet specialation of a
* PentairPacket.
*
* @author Jeff James - initial contribution
*
*/
@NonNullByDefault
public class PentairPumpStatus { // 15 byte packet format
private final Logger logger = LoggerFactory.getLogger(PentairPumpStatus.class);
private static final int RUN = 0 + PentairStandardPacket.STARTOFDATA;
private static final int MODE = 1 + PentairStandardPacket.STARTOFDATA; // Mode in pump status. Means something
// else in pump
// write/response?
private static final int DRIVESTATE = 2 + PentairStandardPacket.STARTOFDATA; // ?? Drivestate in pump status.
// Means something else in
// pump write/response
private static final int WATTSH = 3 + PentairStandardPacket.STARTOFDATA;
private static final int WATTSL = 4 + PentairStandardPacket.STARTOFDATA;
private static final int RPMH = 5 + PentairStandardPacket.STARTOFDATA;
private static final int RPML = 6 + PentairStandardPacket.STARTOFDATA;
private static final int GPM = 7 + PentairStandardPacket.STARTOFDATA;
@SuppressWarnings("unused")
private static final int PPC = 8 + PentairStandardPacket.STARTOFDATA; // not sure what this is? always 0
private static final int STATUS1 = 11 + PentairStandardPacket.STARTOFDATA;
private static final int STATUS2 = 12 + PentairStandardPacket.STARTOFDATA;
private static final int HOUR = 13 + PentairStandardPacket.STARTOFDATA;
private static final int MIN = 14 + PentairStandardPacket.STARTOFDATA;
/** 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 gpm */
public int gpm;
/** byte in packet indicating an error condition */
public int error;
/** byte in packet indicated status */
public int status1;
public int status2;
/** 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;
public void parsePacket(PentairStandardPacket p) {
if (p.getPacketLengthHeader() != 15) {
logger.debug("Pump status packet not 15 bytes long");
return;
}
run = (p.getByte(RUN) == (byte) 0x0A);
mode = p.getByte(MODE);
drivestate = p.getByte(DRIVESTATE);
power = ((p.getByte(WATTSH) & 0xFF) * 256) + (p.getByte(WATTSL) & 0xFF);
rpm = ((p.getByte(RPMH) & 0xFF) * 256) + (p.getByte(RPML) & 0xFF);
gpm = p.getByte(GPM) & 0xFF;
status1 = p.getByte(STATUS1);
status2 = p.getByte(STATUS2);
hour = p.getByte(HOUR);
min = p.getByte(MIN);
}
@Override
public String toString() {
String str = String.format("%02d:%02d run:%b mode:%d power:%d rpm:%d gpm:%d status11:0x%h status12:0x%h", hour,
min, run, mode, power, rpm, gpm, status1, status2);
return str;
}
}

View File

@ -0,0 +1,109 @@
/**
* Copyright (c) 2010-2024 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.parser;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PentairBasePacket } base class is meant to be extended for either a "standard" pentair packet or the
* non-standard intellchlor packet
*
* @author Jeff James - initial contribution
*
*/
@NonNullByDefault
public class PentairBasePacket {
private static final char[] HEXARRAY = "0123456789ABCDEF".toCharArray();
public byte[] buf;
public PentairBasePacket(int l) {
buf = new byte[l];
}
public PentairBasePacket(byte[] buf) {
this(buf, buf.length);
}
public PentairBasePacket(byte[] buf, int l) {
this.buf = new byte[l];
System.arraycopy(buf, 0, this.buf, 0, l);
}
public int getLength() {
return buf.length;
}
public int getByte(int n) {
return (buf[n]) & 0xff;
}
public void setByte(int n, byte b) {
buf[n] = b;
}
/**
* Helper function to convert byte to hex representation
*
* @param b byte to re
* @return 2 character 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 toHexString(byte[] bytes) {
return toHexString(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 toHexString(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);
}
public static String toHexString(ByteBuffer buf) {
return toHexString(buf.array(), buf.limit());
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return toHexString(buf, getLength());
}
}

View File

@ -0,0 +1,133 @@
/**
* Copyright (c) 2010-2024 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.parser;
import java.nio.charset.StandardCharsets;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PentairIntelliChlorPacket } class extends the PentairPacket class to add specific items for the
* IntelliChlor packet format since it does not follow the standard packet format.
*
* @author Jeff James - initial contribution
*
*/
@NonNullByDefault
public class PentairIntelliChlorPacket extends PentairBasePacket {
public static final int DEST = 2;
public static final int ACTION = 3;
// Set Generate %
public static final int SALTOUTPUT = 4;
// Response to set Generate %
public static final int SALINITY = 4;
public static final int STATUS = 5;
// Response to get version
public static final int VERSION = 4;
public static final int NAME = 5;
public static int getPacketDataLength(int command) {
int length = -1;
switch (command) {
case 0x03: // Response to version
length = 17;
break;
case 0x00: // Get status of Chlorinator
case 0x11: // Set salt output level (from controller->chlorinator)
case 0x14:
length = 1;
break;
case 0x01: // Response to Get Status
case 0x12: // status update with salinity and status
length = 2;
break;
}
return length;
}
public PentairIntelliChlorPacket(byte[] buf, int length) {
super(buf, length);
}
public int getVersion() {
if (this.getByte(ACTION) != 0x03) {
return -1;
}
return buf[VERSION] & 0xFF;
}
public int getDest() {
return buf[DEST];
}
public String getName() {
if (this.getByte(ACTION) != 0x03) {
return "";
}
String name = new String(buf, NAME, 16, StandardCharsets.UTF_8);
return name;
}
/*
* Salt Output is available only in packets where the action is 0x11. This is packet sent from the
* controller to the chlorinator to set the salt output to a specific level.
*/
public int getSaltOutput() {
return (this.getByte(ACTION) == 0x11) ? (buf[SALTOUTPUT] & 0xFF) : -1;
}
// Salinity and LED status are sent on a packet with action is 0x12. This is sent from the chlorinator.
public int getSalinity() {
return (this.getByte(ACTION) == 0x12) ? (buf[SALINITY] & 0xFF) * 50 : -1;
}
public boolean getOk() {
return (this.getByte(ACTION) == 0x12) ? ((buf[STATUS] & 0xFF) == 0) || ((buf[STATUS] & 0xFF) == 0x80) : false;
}
public boolean getLowFlow() {
return (this.getByte(ACTION) == 0x12) ? (buf[STATUS] & 0x01) != 0 : false;
}
public boolean getLowSalt() {
return (this.getByte(ACTION) == 0x12) ? (buf[STATUS] & 0x02) != 0 : false;
}
public boolean getVeryLowSalt() {
return (this.getByte(ACTION) == 0x12) ? (buf[STATUS] & 0x04) != 0 : false;
}
public boolean getHighCurrent() {
return (this.getByte(ACTION) == 0x12) ? (buf[STATUS] & 0x08) != 0 : false;
}
public boolean getCleanCell() {
return (this.getByte(ACTION) == 0x12) ? (buf[STATUS] & 0x10) != 0 : false;
}
public boolean getLowVoltage() {
return (this.getByte(ACTION) == 0x12) ? (buf[STATUS] & 0x20) != 0 : false;
}
public boolean getLowWaterTemp() {
return (this.getByte(ACTION) == 0x12) ? (buf[STATUS] & 0x40) != 0 : false;
}
}

View File

@ -0,0 +1,251 @@
/**
* Copyright (c) 2010-2024 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.parser;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairParser } class implements the thread to read and parse the input stream. Once a packet can be
* identified, it locates the
* representative sending Thing and dispositions the packet so it can be further processed.
*
* @author Jeff James - Initial contribution
*
*/
@NonNullByDefault
public class PentairParser implements Runnable {
public static final int MAX_PACKET_SIZE = 50;
private final Logger logger = LoggerFactory.getLogger(PentairParser.class);
private enum ParserState {
WAIT_STARTOFPACKET,
CMD_PENTAIR,
CMD_INTELLICHLOR
};
private @Nullable InputStream reader;
public void setInputStream(InputStream reader) {
this.reader = reader;
}
// Callback interface when a packet is received
public interface CallbackPentairParser {
public void onPentairPacket(PentairStandardPacket p);
public void onIntelliChlorPacket(PentairIntelliChlorPacket p);
public void parserFailureCallback();
};
@Nullable
private CallbackPentairParser callback;
public void setCallback(CallbackPentairParser cb) {
callback = cb;
}
private int getByte() throws IOException {
InputStream reader = Objects.requireNonNull(this.reader, "Reader has not been initialized.");
return reader.read();
}
private int getBytes(ByteBuffer buf, int n) throws IOException {
for (int i = 0; i < n; i++) {
buf.put((byte) getByte());
}
return n;
}
private int calcChecksum(ByteBuffer buf) {
int chksum = 0, i;
for (i = 0; i < buf.limit(); i++) {
chksum += (buf.get() & 0xFF);
}
return chksum;
}
@Override
public void run() {
ByteBuffer buf = ByteBuffer.allocate(MAX_PACKET_SIZE + 10);
int c, c2;
int checksumInPacket, checksumCalc;
int length;
ParserState parserstate = ParserState.WAIT_STARTOFPACKET;
Objects.requireNonNull(this.reader, "Reader stream has not been set.");
while (!Thread.interrupted()) {
try {
c = getByte();
switch (parserstate) {
case WAIT_STARTOFPACKET: // will parse FF FF FF ... 00
if (c == 0xFF) { // for CMD_PENTAIR, we need at lease one 0xFF
do {
c = getByte();
} 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_STARTOFPACKET; // any break caused by invalid packet will go
// back to waiting for a new start of packet
if (c != 0xFF) {
logger.trace("parser: FF00 !FF");
break;
}
buf.clear();
if (getBytes(buf, 6) != 6) { // read enough to get the length
logger.trace("Unable to read 6 bytes");
break;
}
if (buf.get(0) != (byte) 0xA5) {
logger.trace("parser: FF00FF !A5");
break;
}
length = (buf.get(5) & 0xFF);
if (length > MAX_PACKET_SIZE) {
logger.trace("Received packet longer than {} bytes: {}", MAX_PACKET_SIZE, length);
break;
}
// buf should contain A5 00 0F 10 02 1D (A5 00 D S A L)
if (getBytes(buf, length) != length) { // read remaining packet
break;
}
checksumInPacket = (getByte() << 8) & 0xFF00;
checksumInPacket += (getByte() & 0xFF);
buf.flip();
checksumCalc = calcChecksum(buf.duplicate());
if (checksumInPacket != checksumCalc) {
logger.trace("Checksum error: {}!={}-{}", checksumInPacket, checksumCalc,
PentairBasePacket.toHexString(buf));
break;
}
PentairStandardPacket p = new PentairStandardPacket(buf.array(), buf.limit());
logger.trace("[{}] PentairPacket: {}", p.getSource(), p.toString());
CallbackPentairParser callback = this.callback;
if (callback != null) {
callback.onPentairPacket(p);
}
break;
}
case CMD_INTELLICHLOR: { // 10 02 00 12 89 90 xx 10 03
parserstate = ParserState.WAIT_STARTOFPACKET; // any break caused by invalid packet will go back
// to waiting on a new packet frame
buf.clear();
buf.put((byte) 0x10); // need to add back in the initial start of packet since that is included
// in checksum
if ((byte) c != (byte) 0x02) {
break;
}
buf.put((byte) c);
buf.put((byte) getByte()); // Destination
c = (byte) getByte();
buf.put((byte) c); // Command
length = PentairIntelliChlorPacket.getPacketDataLength(c);
int dest = buf.get(2);
if (length == -1) {
logger.debug("[{}] IntelliChlor Packet unseen: command - {}", dest, c & 0xFF);
break;
}
// data bytes + 1 checksum + 0x10, 0x03
if (getBytes(buf, length) != length) {
break;
}
checksumInPacket = getByte();
c = getByte(); // 0x10
c2 = getByte(); // 0x03
// Check to see if closing command is 0x10 and and 0x03
if ((byte) c != (byte) 0x10 || (byte) c2 != (byte) 0x03) {
logger.trace("[{}]Invalid Intellichlor command: {}", dest,
PentairBasePacket.toHexString(buf));
break; // invalid command
}
buf.flip();
checksumCalc = calcChecksum(buf.duplicate());
if ((byte) checksumCalc != (byte) checksumInPacket) {
logger.trace("[{}] Invalid Intellichlor checksum: {}", dest,
PentairBasePacket.toHexString(buf));
break;
}
PentairIntelliChlorPacket pic = new PentairIntelliChlorPacket(buf.array(), buf.limit());
logger.trace("[{}] IntelliChlor Packet: {}", dest, pic.toString());
CallbackPentairParser callback = this.callback;
if (callback != null) {
callback.onIntelliChlorPacket(pic);
}
break;
}
}
} catch (IOException e) {
logger.debug("I/O error while reading from stream: {}", e.getMessage());
Thread.currentThread().interrupt();
CallbackPentairParser callback = this.callback;
if (callback != null) {
callback.parserFailureCallback();
}
break; // exit while loop
// PentairBaseBridgeHandler will monitor this thread and restart if it exits unexpectedly
}
}
logger.trace("msg reader thread exited");
}
}

View File

@ -0,0 +1,126 @@
/**
* Copyright (c) 2010-2024 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.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
*
* { @link PentairStandardPacket } class implements the pentair standard packet format. Most commands sent over the bus
* utilize this format (with the exception of the Intellichlor packets).
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
public class PentairStandardPacket extends PentairBasePacket {
public static final int A5 = 0;
public static final int PREAMBLE = 1;
public static final int DEST = 2;
public static final int SOURCE = 3;
public static final int ACTION = 4;
public static final int LENGTH = 5;
public static final int STARTOFDATA = 6;
/**
* Constructor for an empty packet. Typically used when generating a packet to
* send. Should include all bytes starting with A5, but not including the checksum
*/
public PentairStandardPacket() {
super(6);
buf[0] = (byte) 0xA5;
}
public PentairStandardPacket(byte[] array, int limit) {
super(array, limit);
}
/*
* Constructor to create packet from this p
*/
public PentairStandardPacket(byte[] packet) {
super(packet);
}
/**
* Gets length of packet
*
* @return length of packet
*/
public int getPacketLengthHeader() {
return (buf[LENGTH] & 0xFF);
}
/**
* Sets length of packet
*
* @param length length of packet
*/
public void setPacketLengthHeader(int length) {
if (length > (buf[LENGTH] & 0xFF)) {
buf = new byte[length + 6];
}
buf[LENGTH] = (byte) length;
}
public int getSource() {
return buf[SOURCE];
}
public int getDest() {
return buf[DEST];
}
public int getAction() {
return buf[ACTION];
}
/**
* Calculate checksum of the representative packet.
*
* @return checksum of packet
*/
public int calcChecksum() {
int checksum = 0, i;
for (i = 0; i < getPacketLengthHeader() + 6; i++) {
checksum += buf[i] & 0xFF;
}
return checksum;
}
/**
* Helper function to prepare the packet (including pre-amble and checksum) before being sent
*
* @return
*/
public byte[] wrapPacketToSend() {
int checksum;
byte[] preamble = { (byte) 0xFF, (byte) 0x00, (byte) 0xFF };
byte[] writebuf;
writebuf = new byte[preamble.length + buf.length + 2];
System.arraycopy(preamble, 0, writebuf, 0, preamble.length);
System.arraycopy(this.buf, 0, writebuf, preamble.length, buf.length);
checksum = calcChecksum();
writebuf[writebuf.length - 2] = (byte) ((checksum >> 8) & 0xFF);
writebuf[writebuf.length - 1] = (byte) (checksum & 0xFF);
return writebuf;
}
}

View File

@ -0,0 +1,155 @@
/**
* Copyright (c) 2010-2024 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.utils;
import java.lang.ref.SoftReference;
import java.time.Duration;
import java.util.function.Supplier;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* This is a modified version of the ExpiryCache which adds functions such as getLastKnownValue. It also allows an
* interface via Supplier which will return the value, or through a function which calls putValue.
*
* There must be provided an action in order to retrieve/calculate the value. This action will be called only if the
* answer from the last calculation is not valid anymore, i.e. if it is expired.
*
* @author Christoph Weitkamp - Initial contribution
* @author Martin van Wingerden - Add Duration constructor
* @author Jeff James - Added added getLastKnownValue
*
* @param <V> the type of the value
*/
@NonNullByDefault
public class ExpiringCache<V> {
private final long expiry;
private SoftReference<@Nullable V> value = new SoftReference<>(null);
private long expiresAt;
public interface RefreshAction {
void refresh();
}
public ExpiringCache() {
this.expiry = 0;
}
/**
* Create a new instance.
*
* @param expiry the duration for how long the value stays valid
* @param action the action to retrieve/calculate the value
* @throws IllegalArgumentException For an expire value <=0.
*/
public ExpiringCache(Duration expiry) {
if (expiry.isNegative() || expiry.isZero()) {
throw new IllegalArgumentException("Cache expire time must be greater than 0");
}
this.expiry = expiry.toNanos();
}
/**
* Create a new instance.
*
* @param expiry the duration in milliseconds for how long the value stays valid
* @param action the action to retrieve/calculate the value
*/
public ExpiringCache(long expiry) {
this(Duration.ofMillis(expiry));
}
/**
* Returns the value - possibly from the cache, if it is still valid.
*/
public synchronized @Nullable V getValue(Supplier<@Nullable V> action) {
@Nullable
V cachedValue = value.get();
if (cachedValue == null || isExpired()) {
return refreshValue(action);
}
return cachedValue;
}
/**
* Returns the value - either from the cache or will call the action function which is responsible for calling
* putValue.
*/
public synchronized @Nullable V getValue(RefreshAction action) {
@Nullable
V cachedValue = value.get();
if (cachedValue == null || isExpired()) {
action.refresh();
cachedValue = value.get();
}
return cachedValue;
}
/**
* Returns the last known value
*/
public synchronized @Nullable V getLastKnownValue() {
return value.get();
}
/**
* Puts a new value into the cache.
*
* @param value the new value
*/
public final synchronized void putValue(@Nullable V value) {
this.value = new SoftReference<>(value);
expiresAt = calcExpiresAt();
}
/**
* Invalidates the value in the cache.
*/
public final synchronized void invalidateValue() {
value = new SoftReference<>(null);
expiresAt = 0;
}
/**
* Refreshes and returns the value in the cache.
* If null returned from action.get, the get action should have sued putValue to update the item
*
* @return the new value
*/
public synchronized @Nullable V refreshValue(Supplier<@Nullable V> action) {
@Nullable
V freshValue = action.get();
if (freshValue == null) {
return null;
}
value = new SoftReference<>(freshValue);
expiresAt = calcExpiresAt();
return freshValue;
}
/**
* Checks if the value is expired.
*
* @return true if the value is expired
*/
public boolean isExpired() {
return expiresAt < System.nanoTime();
}
private long calcExpiresAt() {
return System.nanoTime() + expiry;
}
}

View File

@ -5,14 +5,251 @@ addon.pentair.description = This is the binding for Pentair pool systems.
# thing types
thing-type.pentair.easytouch.label = EasyTouch Controller
thing-type.pentair.easytouch.description = Pentair EasyTouch Controller
thing-type.pentair.controller.label = Controller
thing-type.pentair.controller.description = A Pentair Controller such as EasyTouch or IntelliTouch.
thing-type.pentair.controller.group.aux1.label = Aux 1 Circuit
thing-type.pentair.controller.group.aux2.label = Aux 2 Circuit
thing-type.pentair.controller.group.aux3.label = Aux 3 Circuit
thing-type.pentair.controller.group.aux4.label = Aux 4 Circuit
thing-type.pentair.controller.group.aux5.label = Aux 5 Circuit
thing-type.pentair.controller.group.aux6.label = Aux 6 Circuit
thing-type.pentair.controller.group.aux7.label = Aux 7 Circuit
thing-type.pentair.controller.group.aux8.label = Aux 8 Circuit
thing-type.pentair.controller.group.feature1.label = Feature 1
thing-type.pentair.controller.group.feature2.label = Feature 2
thing-type.pentair.controller.group.feature3.label = Feature 3
thing-type.pentair.controller.group.feature4.label = Feature 4
thing-type.pentair.controller.group.feature5.label = Feature 5
thing-type.pentair.controller.group.feature6.label = Feature 6
thing-type.pentair.controller.group.feature7.label = Feature 7
thing-type.pentair.controller.group.feature8.label = Feature 8
thing-type.pentair.controller.group.pool.label = Pool Circuit
thing-type.pentair.controller.group.poolheat.label = Pool Temperature
thing-type.pentair.controller.group.schedule1.label = Schedule 1
thing-type.pentair.controller.group.schedule2.label = Schedule 2
thing-type.pentair.controller.group.schedule3.label = Schedule 3
thing-type.pentair.controller.group.schedule4.label = Schedule 4
thing-type.pentair.controller.group.schedule5.label = Schedule 5
thing-type.pentair.controller.group.schedule6.label = Schedule 6
thing-type.pentair.controller.group.schedule7.label = Schedule 7
thing-type.pentair.controller.group.schedule8.label = Schedule 8
thing-type.pentair.controller.group.schedule9.label = Schedule 9
thing-type.pentair.controller.group.spa.label = Spa Circuit
thing-type.pentair.controller.group.spaheat.label = Spa Temperature
thing-type.pentair.intellichem.label = Intellichem
thing-type.pentair.intellichem.description = A Pentair Intellichem controller.
thing-type.pentair.intellichlor.label = Intellichlor IC40
thing-type.pentair.intellichlor.description = Pentair Intellichlor IC40
thing-type.pentair.intelliflo.label = Intelliflo Pump
thing-type.pentair.intelliflo.description = Pentair Intelliflo Pump
thing-type.pentair.intelliflo.label = Pentair Intelliflo
thing-type.pentair.intelliflo.description = A Pentair Intelliflo pump
thing-type.pentair.ip_bridge.label = IP Bridge
thing-type.pentair.ip_bridge.description = This bridge is for use over a network interface.
thing-type.pentair.ip_bridge.description = This bridge is for used over a network interface.
thing-type.pentair.serial_bridge.label = Pentair-RS485 Serial Bridge
thing-type.pentair.serial_bridge.description = This bridge should be configured when using a USB->RS485 interface.
# thing types config
thing-type.config.pentair.controller.id.label = ID
thing-type.config.pentair.controller.id.description = The ID of the device (in decimal, not hex)
thing-type.config.pentair.controller.synctime.label = Synchronize Time
thing-type.config.pentair.controller.synctime.description = Enables automatic synchronization of the pool controller clock with the system clock
thing-type.config.pentair.intellichem.id.label = ID
thing-type.config.pentair.intellichem.id.description = The ID of the device (in decimal, not hex)
thing-type.config.pentair.intelliflo.id.label = ID
thing-type.config.pentair.intelliflo.id.description = The ID of the device (in decimal, not hex)
thing-type.config.pentair.ip_bridge.address.label = IP Address
thing-type.config.pentair.ip_bridge.address.description = The IP address of the network interface.
thing-type.config.pentair.ip_bridge.discovery.label = Enable Discovery
thing-type.config.pentair.ip_bridge.discovery.description = Enable automatic discovery of devices
thing-type.config.pentair.ip_bridge.id.label = Pentair ID
thing-type.config.pentair.ip_bridge.id.description = The ID to use when sending commands on the Pentair bus (default: 34)
thing-type.config.pentair.ip_bridge.port.label = Port
thing-type.config.pentair.ip_bridge.port.description = The port used to connect to the network interface.
thing-type.config.pentair.serial_bridge.discovery.label = Enable Discovery
thing-type.config.pentair.serial_bridge.discovery.description = Enable automatic discovery of devices
thing-type.config.pentair.serial_bridge.id.label = Pentair ID
thing-type.config.pentair.serial_bridge.id.description = The ID to use to send commands on the Pentair bus (default: 34)
thing-type.config.pentair.serial_bridge.serialPort.label = Serial Port
thing-type.config.pentair.serial_bridge.serialPort.description = The serial port name. Valid values are e.g. COM1 for Windows and /dev/ttyS0 or /dev/ttyUSB0 for Linux.
# channel group types
channel-group-type.pentair.circuit.label = Circuit
channel-group-type.pentair.circuit.description = Circuit
channel-group-type.pentair.feature.label = Feature
channel-group-type.pentair.feature.description = Features
channel-group-type.pentair.heat.label = Heat
channel-group-type.pentair.heat.description = Heat
channel-group-type.pentair.schedule.label = Schedule
channel-group-type.pentair.schedule.description = schedule
channel-group-type.pentair.status.label = Status
channel-group-type.pentair.status.description = General status channels for controller
# channel types
channel-type.pentair.airTemp.label = Air Temperature
channel-type.pentair.airTemp.description = The temperature of the air.
channel-type.pentair.alarmOrp.label = ORP Alarm
channel-type.pentair.alarmOrp.description = ORP alarm reported.
channel-type.pentair.alarmOrpTank.label = ORP Tank Alarm
channel-type.pentair.alarmOrpTank.description = ORP tank alarm reported.
channel-type.pentair.alarmPh.label = PH Alarm
channel-type.pentair.alarmPh.description = PH alarm reported.
channel-type.pentair.alarmPhTank.label = PH Tank Alarm
channel-type.pentair.alarmPhTank.description = PH tank alarm reported.
channel-type.pentair.alarmProbeFault.label = Probe Fault Alarm
channel-type.pentair.alarmProbeFault.description = Probe fault alarm reported.
channel-type.pentair.alarmWaterFlow.label = Water Flow Alarm
channel-type.pentair.alarmWaterFlow.description = Water flow alarm (on = no water flow).
channel-type.pentair.alkalinity.label = Total Alkalinity
channel-type.pentair.alkalinity.description = Total Alkalinity reading (ppm).
channel-type.pentair.auxSwitch.label = Auxillary Switch
channel-type.pentair.auxSwitch.description = The on/off control for this circuit.
channel-type.pentair.calciumHardness.label = Calcium Hardess
channel-type.pentair.calciumHardness.description = Calcium hardness (ppm).
channel-type.pentair.circuitFunction.label = Circuit Function
channel-type.pentair.circuitFunction.description = The function this circuit controls.
channel-type.pentair.circuitName.label = Circuit Name
channel-type.pentair.circuitName.description = The name of this circuit.
channel-type.pentair.cleanCell.label = Clean Cell
channel-type.pentair.cleanCell.description = Clean chlorinator cell.
channel-type.pentair.cyaReading.label = CYA Reading
channel-type.pentair.cyaReading.description = Cyanuric acid reading (ppm).
channel-type.pentair.doseTime.label = Dose Time
channel-type.pentair.doseTime.description = The time a particular chemical has been dosing.
channel-type.pentair.doserStatus.label = Doser Status
channel-type.pentair.doserStatus.description = Whether the chemical is currently dosing.
channel-type.pentair.gpm.label = GPM
channel-type.pentair.gpm.description = Pump GPM (only valid for VF pumps)
channel-type.pentair.heatMode.label = Heat Mode
channel-type.pentair.heatMode.description = The current head mode (None, Heater, Solar Preferred, Solar).
channel-type.pentair.heatMode.state.option.NONE = None
channel-type.pentair.heatMode.state.option.HEATER = Heater
channel-type.pentair.heatMode.state.option.SOLARPREFERRED = Solar Preferred
channel-type.pentair.heatMode.state.option.SOLAR = Solar
channel-type.pentair.heatSetPoint.label = Temperature Set Point
channel-type.pentair.heatSetPoint.description = The set point temperature for this mode.
channel-type.pentair.heatTemperature.label = Water Temperature
channel-type.pentair.heatTemperature.description = The temperature of the water. Only valid when pool pump is running.
channel-type.pentair.heaterDelay.label = Heater Delay
channel-type.pentair.heaterDelay.description = Pump is continuing to run to allow the heater to cool.
channel-type.pentair.heaterState.label = Heater State
channel-type.pentair.heaterState.description = The state of the heater (on, off)
channel-type.pentair.highCurrent.label = High Current
channel-type.pentair.highCurrent.description = Chlorinator drawing high current.
channel-type.pentair.lightMode.label = Light Mode
channel-type.pentair.lightMode.description = The current light mode.
channel-type.pentair.lightMode.state.option.OFF = Off
channel-type.pentair.lightMode.state.option.ON = On
channel-type.pentair.lightMode.state.option.COLORSYNC = Color Sync
channel-type.pentair.lightMode.state.option.COLORSWIM = Color Swim
channel-type.pentair.lightMode.state.option.COLORSET = Color Set
channel-type.pentair.lightMode.state.option.PARTY = Party
channel-type.pentair.lightMode.state.option.ROMANCE = Romance
channel-type.pentair.lightMode.state.option.CARIBBEAN = Caribbean
channel-type.pentair.lightMode.state.option.AMERICAN = American
channel-type.pentair.lightMode.state.option.SUNSET = Sunset
channel-type.pentair.lightMode.state.option.ROYAL = Royal
channel-type.pentair.lightMode.state.option.BLUE = Blue
channel-type.pentair.lightMode.state.option.GREEN = Green
channel-type.pentair.lightMode.state.option.RED = Red
channel-type.pentair.lightMode.state.option.WHITE = White
channel-type.pentair.lightMode.state.option.MAGENTA = Magenta
channel-type.pentair.lowFlow.label = Low Flow
channel-type.pentair.lowFlow.description = Water flow rate is low.
channel-type.pentair.lowSalt.label = Low Salt
channel-type.pentair.lowSalt.description = Low salt level.
channel-type.pentair.lowVoltage.label = Low Voltage
channel-type.pentair.lowVoltage.description = Chlorinator cell is at a low voltage.
channel-type.pentair.lowWaterTemp.label = Low Water Temperature
channel-type.pentair.lowWaterTemp.description = Water temperature is too low for chlorine generation.
channel-type.pentair.lsi.label = LSI
channel-type.pentair.lsi.description = Langelier Saturation Index.
channel-type.pentair.ok.label = Chlorinator OK
channel-type.pentair.ok.description = Chlorinator is operating correctly.
channel-type.pentair.orpDoserType.label = ORP Doser Type
channel-type.pentair.orpDoserType.description = The doser type for ORP (None, ORP).
channel-type.pentair.orpDoserType.state.option.NONE = None
channel-type.pentair.orpDoserType.state.option.ORP = ORP
channel-type.pentair.orpReading.label = ORP Reading
channel-type.pentair.orpReading.description = Current Oxidation Reduction Potential (ORP) reading.
channel-type.pentair.orpSetPoint.label = ORP Set Point
channel-type.pentair.orpSetPoint.description = Oxidation Reduction Potential (ORP) set point.
channel-type.pentair.phDoserType.label = PH Doser Type
channel-type.pentair.phDoserType.description = The doser type for PH (None, CO2, Acid).
channel-type.pentair.phDoserType.state.option.NONE = None
channel-type.pentair.phDoserType.state.option.CO2 = CO2
channel-type.pentair.phDoserType.state.option.ACID = Acid
channel-type.pentair.phReading.label = PH Reading
channel-type.pentair.phReading.description = Current PH reading.
channel-type.pentair.phSetPoint.label = PH Set Point
channel-type.pentair.phSetPoint.description = Current PH set point.
channel-type.pentair.power.label = Power
channel-type.pentair.power.description = Pump power
channel-type.pentair.pumpStatus1.label = Pump Status 1
channel-type.pentair.pumpStatus1.description = Pump Status 1
channel-type.pentair.pumpStatus2.label = Pump Status 2
channel-type.pentair.pumpStatus2.description = Pump Status 2
channel-type.pentair.rpm.label = RPM
channel-type.pentair.rpm.description = Pump RPM
channel-type.pentair.run.label = Pump Running
channel-type.pentair.run.description = Indicator on whether the pump is running or not.
channel-type.pentair.runProgram.label = Run Program
channel-type.pentair.runProgram.description = Run program (0 to stop, # to run)
channel-type.pentair.salinity.label = Salinity (PPM)
channel-type.pentair.salinity.description = Current salt content reading of the water (PPM).
channel-type.pentair.saltLevel.label = Salt Level (PPM)
channel-type.pentair.saltLevel.description = Current salt content reading of the water (PPM).
channel-type.pentair.saltOutput.label = Salt Output (%)
channel-type.pentair.saltOutput.description = Current salt output setting for the chlorinator (%).
channel-type.pentair.scheduleCircuit.label = Circuit
channel-type.pentair.scheduleCircuit.description = The circuit number for the schedule.
channel-type.pentair.scheduleDays.label = Days
channel-type.pentair.scheduleDays.description = A string containing the days the schedule will run (SMTWRFY).
channel-type.pentair.scheduleEnd.label = End Time
channel-type.pentair.scheduleEnd.description = The end time (or duration for Egg Timer) of this schedule.
channel-type.pentair.scheduleStart.label = Start Time
channel-type.pentair.scheduleStart.description = The start time for this schedule.
channel-type.pentair.scheduleString.label = Schedule
channel-type.pentair.scheduleString.description = String format of schedule.
channel-type.pentair.scheduleType.label = Schedule Type
channel-type.pentair.scheduleType.description = Type of schedule (None, Normal, EggTimer, OnceOnly).
channel-type.pentair.scheduleType.state.option.NONE = None
channel-type.pentair.scheduleType.state.option.NORMAL = Normal
channel-type.pentair.scheduleType.state.option.EGGTIMER = Egg Timer
channel-type.pentair.scheduleType.state.option.ONCEONLY = Once Only
channel-type.pentair.serviceMode.label = Service Mode
channel-type.pentair.serviceMode.description = Controller is in service mode
channel-type.pentair.solarState.label = Solar Heater State
channel-type.pentair.solarState.description = The state of the solar heater (on, off)
channel-type.pentair.solarTemp.label = Solar Temperature
channel-type.pentair.solarTemp.description = The temperature of the solar sensor.
channel-type.pentair.tankLevel.label = Tank Level
channel-type.pentair.tankLevel.description = Tank level (1-7).
channel-type.pentair.temperature.label = Temperature
channel-type.pentair.temperature.description = Current temperature.
channel-type.pentair.veryLowSalt.label = Very Low Salt
channel-type.pentair.veryLowSalt.description = Very low salt level.
channel-type.pentair.warningChlorinatorCommError.label = Chlorinator Comm Error
channel-type.pentair.warningChlorinatorCommError.description = Error in communicating with the Chlorinator.
channel-type.pentair.warningInvalidSetup.label = Invalid Setup
channel-type.pentair.warningInvalidSetup.description = Invalid setup for the unit.
channel-type.pentair.warningOrpDailyLimitReached.label = ORP Daily Limit Reached
channel-type.pentair.warningOrpDailyLimitReached.description = Daily limit of ORP dosing has been reached.
channel-type.pentair.warningPhDailyLimitReached.label = PH Daily Limit Reached
channel-type.pentair.warningPhDailyLimitReached.description = Daily limit of PH dosing has been reached.
channel-type.pentair.warningPhLockout.label = PH Lockout Warning
channel-type.pentair.warningPhLockout.description = Unit is in PH Lockout.
# binding
binding.pentair.name = Pentair Binding
binding.pentair.description = This is the binding for Pentair pool systems.
# thing types
thing-type.pentair.easytouch.label = EasyTouch Controller
thing-type.pentair.easytouch.description = Pentair EasyTouch Controller
thing-type.pentair.pentair_serial_bridge.label = Serial Bridge
thing-type.pentair.pentair_serial_bridge.description = This bridge is used when using a USB->RS485 interface.
@ -22,14 +259,6 @@ thing-type.config.pentair.easytouch.id.label = ID
thing-type.config.pentair.easytouch.id.description = The ID of the device (in decimal, not hex)
thing-type.config.pentair.intellichlor.id.label = ID
thing-type.config.pentair.intellichlor.id.description = The ID of the device (in decimal, not hex)
thing-type.config.pentair.intelliflo.id.label = ID
thing-type.config.pentair.intelliflo.id.description = The ID of the device (in decimal, not hex)
thing-type.config.pentair.ip_bridge.address.label = IP Address
thing-type.config.pentair.ip_bridge.address.description = The IP address to connect to.
thing-type.config.pentair.ip_bridge.id.label = Pentair ID
thing-type.config.pentair.ip_bridge.id.description = The ID to use to send commands on the Pentair bus (default: 34)
thing-type.config.pentair.ip_bridge.port.label = Port
thing-type.config.pentair.ip_bridge.port.description = The port to connect to.
thing-type.config.pentair.pentair_serial_bridge.id.label = Pentair ID
thing-type.config.pentair.pentair_serial_bridge.id.description = The ID to use to send commands on the Pentair bus (default: 34)
thing-type.config.pentair.pentair_serial_bridge.serialPort.label = Serial Port
@ -57,20 +286,14 @@ channel-type.pentair.poolsetpoint.label = Pool Temperature Set Point
channel-type.pentair.poolsetpoint.description = Pool temperature set point
channel-type.pentair.pooltemp.label = Pool Water Temperature
channel-type.pentair.pooltemp.description = Pool water temperature. Only valid when pool pump is running and in pool mode.
channel-type.pentair.power.label = Power
channel-type.pentair.power.description = Pump power
channel-type.pentair.ppc.label = PPC
channel-type.pentair.ppc.description = Pump PPC
channel-type.pentair.pumperror.label = Pump Error
channel-type.pentair.pumperror.description = Pump Error
channel-type.pentair.pumpmode.label = Pump Mode
channel-type.pentair.pumpmode.description = Pump mode
channel-type.pentair.rpm.label = RPM
channel-type.pentair.rpm.description = Pump RPM
channel-type.pentair.runswitch.label = Pump Running
channel-type.pentair.runswitch.description = Indicator on whether the pump is running or not.
channel-type.pentair.salinity.label = Salinity (PPM)
channel-type.pentair.salinity.description = Current salt content reading of the water (PPM).
channel-type.pentair.saltoutput.label = Salt Output (%)
channel-type.pentair.saltoutput.description = Current salt output setting for the chlorinator (%).
channel-type.pentair.solartemp.label = Solar Temperature
@ -79,3 +302,18 @@ channel-type.pentair.spasetpoint.label = Spa Temperature Set Point
channel-type.pentair.spasetpoint.description = Spa temperature set point
channel-type.pentair.spatemp.label = Spa Water Temperature
channel-type.pentair.spatemp.description = Spa water temperature. Only valide when in spa mode.
# offline configuration errors
offline.configuration-error.bridge-missing = The Pentair bridge is missing.
offline.configuration-error.bridge-duplicate = A Pentair bridge is already setup. This binding will only support a single bridge.
offline.configuration-error.duplicate-id = A Pentair thing has already been created with this id.
offline.configuration-error.duplicate-controller = A Pentair controller has already been created. Only a single controller is supported.
offline.configuration-error.duplicate-intllichlor = A Pentair IntelliChlor device has already been created. Only a single IntelliChlor is supported.
offline.communication-error.iostream-error = A write or read iostream was unable to be created.
offline.communication-error.ip-stream-error = An error occurred in accessing the ip port.
offline.configuration-error.serial-port-empty = There is no configured serial port.
offline.communication-error.serial-port-busy = The serial port is being used by another application:
offline.communication-error.serial-port-not-found = Serial port does not exist:
offline.communication-error.serial-port-open-failed = Serial port cannot be opened:
offline.communication-error.serial-port-error = An exception occurred in accessing the serial port:

View File

@ -0,0 +1,350 @@
<?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="controller">
<supported-bridge-type-refs>
<bridge-type-ref id="ip_bridge"/>
<bridge-type-ref id="serial_bridge"/>
</supported-bridge-type-refs>
<label>Controller</label>
<description>A Pentair Controller such as EasyTouch or IntelliTouch.</description>
<channel-groups>
<channel-group id="poolheat" typeId="heat">
<label>Pool Temperature</label>
</channel-group>
<channel-group id="spaheat" typeId="heat">
<label>Spa Temperature</label>
</channel-group>
<channel-group id="pool" typeId="circuit">
<label>Pool Circuit</label>
</channel-group>
<channel-group id="spa" typeId="circuit">
<label>Spa Circuit</label>
</channel-group>
<channel-group id="aux1" typeId="circuit">
<label>Aux 1 Circuit</label>
</channel-group>
<channel-group id="aux2" typeId="circuit">
<label>Aux 2 Circuit</label>
</channel-group>
<channel-group id="aux3" typeId="circuit">
<label>Aux 3 Circuit</label>
</channel-group>
<channel-group id="aux4" typeId="circuit">
<label>Aux 4 Circuit</label>
</channel-group>
<channel-group id="aux5" typeId="circuit">
<label>Aux 5 Circuit</label>
</channel-group>
<channel-group id="aux6" typeId="circuit">
<label>Aux 6 Circuit</label>
</channel-group>
<channel-group id="aux7" typeId="circuit">
<label>Aux 7 Circuit</label>
</channel-group>
<channel-group id="aux8" typeId="circuit">
<label>Aux 8 Circuit</label>
</channel-group>
<channel-group id="feature1" typeId="feature">
<label>Feature 1</label>
</channel-group>
<channel-group id="feature2" typeId="feature">
<label>Feature 2</label>
</channel-group>
<channel-group id="feature3" typeId="feature">
<label>Feature 3</label>
</channel-group>
<channel-group id="feature4" typeId="feature">
<label>Feature 4</label>
</channel-group>
<channel-group id="feature5" typeId="feature">
<label>Feature 5</label>
</channel-group>
<channel-group id="feature6" typeId="feature">
<label>Feature 6</label>
</channel-group>
<channel-group id="feature7" typeId="feature">
<label>Feature 7</label>
</channel-group>
<channel-group id="feature8" typeId="feature">
<label>Feature 8</label>
</channel-group>
<channel-group id="schedule1" typeId="schedule">
<label>Schedule 1</label>
</channel-group>
<channel-group id="schedule2" typeId="schedule">
<label>Schedule 2</label>
</channel-group>
<channel-group id="schedule3" typeId="schedule">
<label>Schedule 3</label>
</channel-group>
<channel-group id="schedule4" typeId="schedule">
<label>Schedule 4</label>
</channel-group>
<channel-group id="schedule5" typeId="schedule">
<label>Schedule 5</label>
</channel-group>
<channel-group id="schedule6" typeId="schedule">
<label>Schedule 6</label>
</channel-group>
<channel-group id="schedule7" typeId="schedule">
<label>Schedule 7</label>
</channel-group>
<channel-group id="schedule8" typeId="schedule">
<label>Schedule 8</label>
</channel-group>
<channel-group id="schedule9" typeId="schedule">
<label>Schedule 9</label>
</channel-group>
<channel-group id="status" typeId="status"/>
</channel-groups>
<properties>
<property name="firmwareVersion">Firmware Version</property>
</properties>
<representation-property>id</representation-property>
<config-description>
<parameter name="id" type="integer">
<label>ID</label>
<description>The ID of the device (in decimal, not hex)</description>
<default>16</default>
</parameter>
<parameter name="synctime" type="boolean">
<label>Synchronize Time</label>
<description>Enables automatic synchronization of the pool controller clock with the system clock</description>
<default>true</default>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="circuit">
<label>Circuit</label>
<description>Circuit</description>
<channels>
<channel id="switch" typeId="auxSwitch"/>
<channel id="name" typeId="circuitName"/>
<channel id="function" typeId="circuitFunction"/>
</channels>
</channel-group-type>
<channel-group-type id="feature">
<label>Feature</label>
<description>Features</description>
<channels>
<channel id="switch" typeId="auxSwitch"/>
<channel id="name" typeId="circuitName"/>
<channel id="function" typeId="circuitFunction"/>
</channels>
</channel-group-type>
<channel-group-type id="heat">
<label>Heat</label>
<description>Heat</description>
<channels>
<channel id="heatmode" typeId="heatMode"/>
<channel id="setpoint" typeId="heatSetPoint"/>
<channel id="temperature" typeId="heatTemperature"/>
</channels>
</channel-group-type>
<channel-group-type id="schedule">
<label>Schedule</label>
<description>schedule</description>
<channels>
<channel id="schedule" typeId="scheduleString"/>
<channel id="type" typeId="scheduleType"/>
<channel id="start" typeId="scheduleStart"/>
<channel id="end" typeId="scheduleEnd"/>
<channel id="circuit" typeId="scheduleCircuit"/>
<channel id="days" typeId="scheduleDays"/>
</channels>
</channel-group-type>
<channel-group-type id="status">
<label>Status</label>
<description>General status channels for controller</description>
<channels>
<channel id="lightmode" typeId="lightMode"/>
<channel id="solartemperature" typeId="solarTemp"/>
<channel id="airtemperature" typeId="airTemp"/>
<channel id="heaterdelay" typeId="heaterDelay"/>
<channel id="servicemode" typeId="serviceMode"/>
<channel id="solaron" typeId="solarState"/>
<channel id="heateron" typeId="heaterState"/>
</channels>
</channel-group-type>
<channel-type id="heatTemperature">
<item-type>Number:Temperature</item-type>
<label>Water Temperature</label>
<description>The temperature of the water. Only valid when pool pump is running.</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="solarTemp">
<item-type>Number:Temperature</item-type>
<label>Solar Temperature</label>
<description>The temperature of the solar sensor.</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="airTemp">
<item-type>Number:Temperature</item-type>
<label>Air Temperature</label>
<description>The temperature of the air.</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="auxSwitch">
<item-type>Switch</item-type>
<label>Auxillary Switch</label>
<description>The on/off control for this circuit.</description>
</channel-type>
<channel-type id="circuitName">
<item-type>String</item-type>
<label>Circuit Name</label>
<description>The name of this circuit.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="circuitFunction">
<item-type>String</item-type>
<label>Circuit Function</label>
<description>The function this circuit controls.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="heatMode">
<item-type>String</item-type>
<label>Heat Mode</label>
<description>The current head mode (None, Heater, Solar Preferred, Solar).</description>
<state readOnly="true">
<options>
<option value="NONE">None</option>
<option value="HEATER">Heater</option>
<option value="SOLARPREFERRED">Solar Preferred</option>
<option value="SOLAR">Solar</option>
</options>
</state>
</channel-type>
<channel-type id="heatSetPoint">
<item-type>Number:Temperature</item-type>
<label>Temperature Set Point</label>
<description>The set point temperature for this mode.</description>
<state pattern="%d %unit%" min="15" max="105" step="1"/>
</channel-type>
<channel-type id="lightMode">
<item-type>String</item-type>
<label>Light Mode</label>
<description>The current light mode.</description>
<state>
<options>
<option value="OFF">Off</option>
<option value="ON">On</option>
<option value="COLORSYNC">Color Sync</option>
<option value="COLORSWIM">Color Swim</option>
<option value="COLORSET">Color Set</option>
<option value="PARTY">Party</option>
<option value="ROMANCE">Romance</option>
<option value="CARIBBEAN">Caribbean</option>
<option value="AMERICAN">American</option>
<option value="SUNSET">Sunset</option>
<option value="ROYAL">Royal</option>
<option value="BLUE">Blue</option>
<option value="GREEN">Green</option>
<option value="RED">Red</option>
<option value="WHITE">White</option>
<option value="MAGENTA">Magenta</option>
</options>
</state>
<autoUpdatePolicy>recommend</autoUpdatePolicy>
</channel-type>
<channel-type id="scheduleType">
<item-type>String</item-type>
<label>Schedule Type</label>
<description>Type of schedule (None, Normal, EggTimer, OnceOnly).</description>
<state>
<options>
<option value="NONE">None</option>
<option value="NORMAL">Normal</option>
<option value="EGGTIMER">Egg Timer</option>
<option value="ONCEONLY">Once Only</option>
</options>
</state>
</channel-type>
<channel-type id="scheduleString">
<item-type>String</item-type>
<label>Schedule</label>
<description>String format of schedule.</description>
</channel-type>
<channel-type id="scheduleStart">
<item-type>Number:Time</item-type>
<label>Start Time</label>
<description>The start time for this schedule.</description>
<state min="0" max="1440"/>
</channel-type>
<channel-type id="scheduleEnd">
<item-type>Number:Time</item-type>
<label>End Time</label>
<description>The end time (or duration for Egg Timer) of this schedule.</description>
<state min="0" max="1440"/>
</channel-type>
<channel-type id="scheduleCircuit">
<item-type>Number</item-type>
<label>Circuit</label>
<description>The circuit number for the schedule.</description>
</channel-type>
<channel-type id="scheduleDays">
<item-type>String</item-type>
<label>Days</label>
<description>A string containing the days the schedule will run (SMTWRFY).</description>
</channel-type>
<channel-type id="serviceMode">
<item-type>Switch</item-type>
<label>Service Mode</label>
<description>Controller is in service mode</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="heaterDelay">
<item-type>Switch</item-type>
<label>Heater Delay</label>
<description>Pump is continuing to run to allow the heater to cool.</description>
</channel-type>
<channel-type id="solarState">
<item-type>Switch</item-type>
<label>Solar Heater State</label>
<description>The state of the solar heater (on, off)</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="heaterState">
<item-type>Switch</item-type>
<label>Heater State</label>
<description>The state of the heater (on, off)</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

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

View File

@ -0,0 +1,258 @@
<?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="intellichem">
<supported-bridge-type-refs>
<bridge-type-ref id="ip_bridge"/>
<bridge-type-ref id="serial_bridge"/>
</supported-bridge-type-refs>
<label>Intellichem</label>
<description>A Pentair Intellichem controller.</description>
<channels>
<channel id="phReading" typeId="phReading"/>
<channel id="orpReading" typeId="orpReading"/>
<channel id="phSetPoint" typeId="phSetPoint"/>
<channel id="orpSetPoint" typeId="orpSetPoint"/>
<channel id="tank1Level" typeId="tankLevel"/>
<channel id="tank2Level" typeId="tankLevel"/>
<channel id="calciumHardness" typeId="calciumHardness"/>
<channel id="cyaReading" typeId="cyaReading"/>
<channel id="alkalinity" typeId="alkalinity"/>
<channel id="phDoserType" typeId="phDoserType"/>
<channel id="orpDoserType" typeId="orpDoserType"/>
<channel id="phDoserStatus" typeId="doserStatus"/>
<channel id="orpDoserStatus" typeId="doserStatus"/>
<channel id="phDoseTime" typeId="doseTime"/>
<channel id="orpDoseTime" typeId="doseTime"/>
<channel id="lsi" typeId="lsi"/>
<channel id="saltLevel" typeId="saltlevel"/>
<channel id="temperature" typeId="temperature"/>
<channel id="alarmWaterflow" typeId="alarmWaterFlow"/>
<channel id="alarmPh" typeId="alarmPh"/>
<channel id="alarmOrp" typeId="alarmOrp"/>
<channel id="alarmPhTank" typeId="alarmPhTank"/>
<channel id="alarmOrpTank" typeId="alarmOrpTank"/>
<channel id="alarmProbeFault" typeId="alarmProbeFault"/>
<channel id="warningPhLockout" typeId="warningPhLockout"/>
<channel id="warningPhDailyLimitReached" typeId="warningPhDailyLimitReached"/>
<channel id="warningOrpDailyLimitReached" typeId="warningOrpDailyLimitReached"/>
<channel id="warningInvalidSetup" typeId="warningInvalidSetup"/>
<channel id="warningChloinatorCommError" typeId="warningChlorinatorCommError"/>
</channels>
<properties>
<property name="firmwareVersion">Firmware Version</property>
</properties>
<representation-property>id</representation-property>
<config-description>
<parameter name="id" type="integer">
<label>ID</label>
<description>The ID of the device (in decimal, not hex)</description>
<default>144</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="phReading">
<item-type>Number</item-type>
<label>PH Reading</label>
<description>Current PH reading.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="orpReading">
<item-type>Number</item-type>
<label>ORP Reading</label>
<description>Current Oxidation Reduction Potential (ORP) reading.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="phSetPoint">
<item-type>Number</item-type>
<label>PH Set Point</label>
<description>Current PH set point.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="orpSetPoint">
<item-type>Number</item-type>
<label>ORP Set Point</label>
<description>Oxidation Reduction Potential (ORP) set point.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="tankLevel">
<item-type>Number</item-type>
<label>Tank Level</label>
<description>Tank level (1-7).</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="calciumHardness">
<item-type unitHint="ppm">Number:Dimensionless</item-type>
<label>Calcium Hardess</label>
<description>Calcium hardness (ppm).</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="cyaReading">
<item-type unitHint="ppm">Number:Dimensionless</item-type>
<label>CYA Reading</label>
<description>Cyanuric acid reading (ppm).</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="alkalinity">
<item-type unitHint="ppm">Number:Dimensionless</item-type>
<label>Total Alkalinity</label>
<description>Total Alkalinity reading (ppm).</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="phDoserType">
<item-type>String</item-type>
<label>PH Doser Type</label>
<description>The doser type for PH (None, CO2, Acid).</description>
<state readOnly="true">
<options>
<option value="NONE">None</option>
<option value="CO2">CO2</option>
<option value="ACID">Acid</option>
</options>
</state>
</channel-type>
<channel-type id="orpDoserType">
<item-type>String</item-type>
<label>ORP Doser Type</label>
<description>The doser type for ORP (None, ORP).</description>
<state readOnly="true">
<options>
<option value="NONE">None</option>
<option value="ORP">ORP</option>
</options>
</state>
</channel-type>
<channel-type id="doserStatus">
<item-type>Switch</item-type>
<label>Doser Status</label>
<description>Whether the chemical is currently dosing.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="doseTime">
<item-type>Number:Time</item-type>
<label>Dose Time</label>
<description>The time a particular chemical has been dosing.</description>
</channel-type>
<channel-type id="lsi">
<item-type>Number</item-type>
<label>LSI</label>
<description>Langelier Saturation Index.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="saltLevel">
<item-type unitHint="ppm">Number:Dimensionless</item-type>
<label>Salt Level (PPM)</label>
<description>Current salt content reading of the water (PPM).</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Current temperature.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="alarmWaterFlow">
<item-type>Switch</item-type>
<label>Water Flow Alarm</label>
<description>Water flow alarm (on = no water flow).</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="alarmPh">
<item-type>Switch</item-type>
<label>PH Alarm</label>
<description>PH alarm reported.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="alarmOrp">
<item-type>Switch</item-type>
<label>ORP Alarm</label>
<description>ORP alarm reported.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="alarmPhTank">
<item-type>Switch</item-type>
<label>PH Tank Alarm</label>
<description>PH tank alarm reported.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="alarmOrpTank">
<item-type>Switch</item-type>
<label>ORP Tank Alarm</label>
<description>ORP tank alarm reported.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="alarmProbeFault">
<item-type>Switch</item-type>
<label>Probe Fault Alarm</label>
<description>Probe fault alarm reported.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="warningPhLockout">
<item-type>Switch</item-type>
<label>PH Lockout Warning</label>
<description>Unit is in PH Lockout.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="warningPhDailyLimitReached">
<item-type>Switch</item-type>
<label>PH Daily Limit Reached</label>
<description>Daily limit of PH dosing has been reached.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="warningOrpDailyLimitReached">
<item-type>Switch</item-type>
<label>ORP Daily Limit Reached</label>
<description>Daily limit of ORP dosing has been reached.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="warningInvalidSetup">
<item-type>Switch</item-type>
<label>Invalid Setup</label>
<description>Invalid setup for the unit.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="warningChlorinatorCommError">
<item-type>Switch</item-type>
<label>Chlorinator Comm Error</label>
<description>Error in communicating with the Chlorinator.</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -7,38 +7,101 @@
<thing-type id="intellichlor">
<supported-bridge-type-refs>
<bridge-type-ref id="ip_bridge"/>
<bridge-type-ref id="pentair_serial_bridge"/>
<bridge-type-ref id="serial_bridge"/>
</supported-bridge-type-refs>
<label>Intellichlor IC40</label>
<description>Pentair Intellichlor IC40</description>
<channels>
<channel id="saltoutput" typeId="saltoutput"/>
<channel id="saltOutput" typeId="saltOutput"/>
<channel id="salinity" typeId="salinity"/>
<channel id="ok" typeId="ok"/>
<channel id="lowFlow" typeId="lowFlow"/>
<channel id="lowSalt" typeId="lowSalt"/>
<channel id="veryLowSalt" typeId="veryLowSalt"/>
<channel id="highCurrent" typeId="highCurrent"/>
<channel id="cleanCell" typeId="cleanCell"/>
<channel id="lowVoltage" typeId="lowVoltage"/>
<channel id="lowWaterTemp" typeId="lowWaterTemp"/>
</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>
<properties>
<property name="version">Version</property>
<property name="model">Model</property>
</properties>
<representation-property>id</representation-property>
</thing-type>
<channel-type id="saltoutput">
<item-type>Number</item-type>
<channel-type id="saltOutput">
<item-type unitHint="%">Number:Dimensionless</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>
<item-type unitHint="ppm">Number:Dimensionless</item-type>
<label>Salinity (PPM)</label>
<description>Current salt content reading of the water (PPM).</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="ok">
<item-type>Switch</item-type>
<label>Chlorinator OK</label>
<description>Chlorinator is operating correctly.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="lowFlow">
<item-type>Switch</item-type>
<label>Low Flow</label>
<description>Water flow rate is low.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="lowSalt">
<item-type>Switch</item-type>
<label>Low Salt</label>
<description>Low salt level.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="veryLowSalt">
<item-type>Switch</item-type>
<label>Very Low Salt</label>
<description>Very low salt level.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="highCurrent">
<item-type>Switch</item-type>
<label>High Current</label>
<description>Chlorinator drawing high current.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="cleanCell">
<item-type>Switch</item-type>
<label>Clean Cell</label>
<description>Clean chlorinator cell.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="lowVoltage">
<item-type>Switch</item-type>
<label>Low Voltage</label>
<description>Chlorinator cell is at a low voltage.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="lowWaterTemp">
<item-type>Switch</item-type>
<label>Low Water Temperature</label>
<description>Water temperature is too low for chlorine generation.</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -7,23 +7,26 @@
<thing-type id="intelliflo">
<supported-bridge-type-refs>
<bridge-type-ref id="ip_bridge"/>
<bridge-type-ref id="pentair_serial_bridge"/>
<bridge-type-ref id="serial_bridge"/>
</supported-bridge-type-refs>
<label>Intelliflo Pump</label>
<description>Pentair Intelliflo Pump</description>
<label>Pentair Intelliflo</label>
<description>A Pentair Intelliflo pump</description>
<channels>
<channel id="run" typeId="runswitch"/>
<channel id="mode" typeId="pumpmode"/>
<channel id="run" typeId="run"/>
<channel id="rpm" typeId="rpm"/>
<channel id="gpm" typeId="gpm"/>
<channel id="power" typeId="power"/>
<channel id="ppc" typeId="ppc"/>
<channel id="error" typeId="pumperror"/>
<channel id="status1" typeId="pumpStatus1"/>
<channel id="status2" typeId='pumpStatus2'/>
<channel id="runProgram" typeId="runProgram"/>
</channels>
<representation-property>id</representation-property>
<config-description>
<parameter name="id" type="integer" required="false">
<parameter name="id" type="integer">
<label>ID</label>
<description>The ID of the device (in decimal, not hex)</description>
<default>96</default>
@ -31,14 +34,7 @@
</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">
<channel-type id="run">
<item-type>Switch</item-type>
<label>Pump Running</label>
<description>Indicator on whether the pump is running or not.</description>
@ -49,28 +45,42 @@
<item-type>Number</item-type>
<label>RPM</label>
<description>Pump RPM</description>
<state readOnly="true"/>
<state min="400" max="3450" step="5" readOnly="false"></state>
</channel-type>
<channel-type id="gpm">
<item-type>Number:VolumetricFlowRate</item-type>
<label>GPM</label>
<description>Pump GPM (only valid for VF pumps)</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="power">
<item-type>Number</item-type>
<item-type>Number:Power</item-type>
<label>Power</label>
<description>Pump power</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="pumpStatus1">
<item-type>Number</item-type>
<label>Pump Status 1</label>
<description>Pump Status 1</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="ppc">
<channel-type id="pumpStatus2">
<item-type>Number</item-type>
<label>PPC</label>
<description>Pump PPC</description>
<label>Pump Status 2</label>
<description>Pump Status 2</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="pumperror">
<channel-type id="runProgram">
<item-type>Number</item-type>
<label>Pump Error</label>
<description>Pump Error</description>
<state readOnly="true"/>
<label>Run Program</label>
<description>Run program (0 to stop, # to run)</description>
<state min="0" max="4"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -6,25 +6,32 @@
<bridge-type id="ip_bridge">
<label>IP Bridge</label>
<description>This bridge is for use over a network interface.</description>
<description>This bridge is for used 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>
<description>The IP address of the network interface.</description>
<context>network-address</context>
<default>127.0.0.1</default>
</parameter>
<parameter name="port" type="integer" required="false">
<label>Port</label>
<description>The port to connect to.</description>
<description>The port used to connect to the network interface.</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>
<description>The ID to use when sending commands on the Pentair bus (default: 34)</description>
<default>34</default>
</parameter>
<parameter name="discovery" type="boolean">
<label>Enable Discovery</label>
<description>Enable automatic discovery of devices</description>
<default>true</default>
</parameter>
</config-description>
</bridge-type>

View File

@ -4,9 +4,9 @@
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>
<bridge-type id="serial_bridge">
<label>Pentair-RS485 Serial Bridge</label>
<description>This bridge should be configured when using a USB->RS485 interface.</description>
<config-description>
<parameter name="serialPort" type="text" required="true">
<label>Serial Port</label>
@ -18,6 +18,12 @@
<description>The ID to use to send commands on the Pentair bus (default: 34)</description>
<default>34</default>
</parameter>
<parameter name="discovery" type="boolean">
<label>Enable Discovery</label>
<description>Enable automatic discovery of devices</description>
<default>true</default>
</parameter>
</config-description>
</bridge-type>

View File

@ -0,0 +1,740 @@
ffffffffffffffff00ffa5240f10021d083a000100000000002000000004
4a4a0000440000000400007ce6000d03b9ff00ffa50060100401ff0219ff
00ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa5001060
06010a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202
ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202005c02ee
000000000001151f02baffffffffffffffff00ffa5240f10021d083a0001
000000000020000000044a4a0000440000000400007ce6000d03b9ffffff
ffffffffff00ffa5240f10021d083a0001000000000020000000044a4a00
00440000000400007ce6000d03b9ff00ffa50060100401ff0219ff00ffa5
0010600401ff0219ff00ffa500601006010a0126ff00ffa500106006010a
0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208
ffffffffffffffff00ffa5240f10080d4a4a444e5e040000000000000002
85ff00ffa50060100700011cff00ffa5001060070f0a0202005a02ee0000
00000001151f02b810025014007610031002000300496e74656c6c696368
6c6f722d2d3430bc1003ffffffffffffffff00ffa5240f10021d083b0001
000000000020000000044a4a0000440000000400007ce6000d03baff00ff
a50060100401ff0219ff00ffa50010600401ff0219ff00ffa50060100401
ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00ff
a500106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa50010
60010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202
005c02ee000000000001151f02baffffffffffffffff00ffa5240f10021d
083b0001000000000020000000044a4a0000440000000400007ce6000d03
baffffffffffffffff00ffa5240f10021d083b0001000000000020000000
044a4a0000440000000400007ce6000d03baff00ffa50060100401ff0219
ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa50010
6006010a0126ff00ffa5006010010402c402ee02d0ff00ffa50010600102
02ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202005a02
ee000000000001151f02b8ffffffffffffffff00ffa5240f10021d083b00
01000000000020000000044a4a0000440000000400007ce6000d03baffff
ffffffffffff00ffa5240f10021d083b0001000000000020000000044a4a
0000440000000400007ce6000d03baff00ffa50060100401ff0219ff00ff
a50010600401ff0219ff00ffa500601006010a0126ff00ffa50010600601
0a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee02
08ff00ffa50060100700011cff00ffa5001060070f0a0202005b02ee0000
00000001151f02b9ffffffffffffffff00ffa5240f10021d083b00010000
00000020000000044a4a0000440000000400007ce6000d03baffffffffff
ffffff00ffa5240f10021d083b0001000000000020000000044a4a000044
0000000400007ce6000d03baff00ffa50060100401ff0219ff00ffa50010
600401ff0219ff00ffa500601006010a0126ff00ffa500106006010a0126
ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208ff00
ffa50060100700011cff00ffa5001060070f0a0202005b02ee0000000000
01151f02b9ffffffffffffffff00ffa5240f10021d083b00010000000000
20000000044a4a0000440000000400007ce6000d03baffffffffffffffff
00ffa5240f10021d083b0001000000000020000000044a4a000044000000
0400007ce6000d03baff00ffa50060100401ff0219ff00ffa50010600401
ff0219ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ff
a500601006010a0126ff00ffa500106006010a0126ff00ffa50060100104
02c402ee02d0ff00ffa5001060010202ee0208ff00ffa50060100700011c
ff00ffa5001060070f0a0202005c02ee000000000001151f02baffffffff
ffffffff00ffa5240f10021d083b0001000000000020000000044a4a0000
440000000400007ce6000d03baffffffffffffffff00ffa5240f10021d08
3b0001000000000020000000044a4a0000440000000400007ce6000d03ba
ffffffffffffffff00ffa5240f10021d083b000100000000002000000004
4a4a0000440000000400007ce6000d03baffffffffffffffff00ffa5240f
10021d083b0001000000000020000000044a4a0000440000000400007ce6
000d03baffffffffffffffff00ffa5240f10021d083b0001000000000020
000000044a4a0000440000000400007ce6000d03baffffffffffffffff00
ffa5240f10021d083b0001000000000020000000044a4a00004400000004
00007ce6000d03baffffffffffffffff00ffa5240f10021d083b00010000
00000020000000044a4a0000440000000400007ce6000d03ba1002501100
731003100200124c81f11003ffffffffffffff
ff00ffa5240f10021d083b0001000000000020000000044a4a0000440000000400007ce6000d03baff
00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa5006010
06010a0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee
02d0ff00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa5
001060070f0a0202005b02ee000000000001151f02b9ffffffffffffffff
00ffa5240f10021d083b0001000000000020000000044a4a000044000000
0400007ce6000d03baffffffffffffffff00ffa5240f10021d083b000100
0000000020000000044a4a0000440000000400007ce6000d03baff00ffa5
0060100401ff0219ff00ffa50010600401ff0219ff00ffa50060100401ff
0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa5
00106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060
010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a020200
5c02ee000000000001151f02baffffffffffffffff00ffa5240f10021d08
3b0001000000000020000000044a4a0000440000000400007ce6000d03ba
ffffffffffffffff00ffa5240f10021d083b000100000000002000000004
4a4a0000440000000400007ce6000d03baff00ffa50060100401ff0219ff
00ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa5001060
06010a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202
ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202005b02ee
000000000001152002baffffffffffffffff00ffa5240f10021d083b0001
000000000020000000044a4a0000440000000400007ce6000d03baffffff
ffffffffff00ffa5240f10021d083b0001000000000020000000044a4a00
00440000000400007ce6000d03baff00ffa50060100401ff0219ff00ffa5
0010600401ff0219ff00ffa500601006010a0126ff00ffa500106006010a
0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208
ff00ffa50060100700011cff00ffa5001060070f0a0202005b02ee000000
000001152002baffffffffffffffff00ffa5240f10021d083b0001000000
000020000000044a4a0000440000000400007ce6000d03baffffffffffff
ffff00ffa5240f10021d083b0001000000000020000000044a4a00004400
00000400007ce6000d03baff00ffa50060100401ff0219ff00ffa5001060
0401ff0219ff00ffa500601006010a0126ff00ffa500106006010a0126ff
00ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208ff00ff
a50060100700011cff00ffa5001060070f0a0202005c02ee000000000001
152002bb1002501100731003100200124c81f11003ffffffffffffffff00
ffa5240f10021d083b0001000000000020000000044a4a00004400000004
00007ce6000d03baff00ffa50060100401ff0219ff00ffa50010600401ff
0219ff00ffa500601006010a0126ff00ffa500106006010a0126ff00ffa5
006010010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa50060
100700011cff00ffa5001060070f0a0202005b02ee000000000001152002
baffffffffffffffff00ffa5240f10021d083b0001000000000020000000
044a4a0000440000000400007ce6000d03baffffffffffffffff00ffa524
0f10021d083b0001000000000020000000044a4a0000440000000400007c
e6000d03baff00ffa50060100401ff0219ff00ffa50010600401ff0219ff
00ffa500601006010a0126ff00ffa500106006010a0126ff00ffa5006010
010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa50060100700
011cff00ffa5001060070f0a0202005c02ee000000000001152002bbffff
ffffffffffff00ffa5240f10021d083b0001000000000020000000044a4a
0000440000000400007ce6000d03baffffffffffffffff00ffa5240f1002
1d083b0001000000000020000000044a4a0000440000000400007ce6000d
03baff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa5
0060100401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a
0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff
00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa5001060
070f0a0202005902ee000000000001152002b8ffffffffffffffff00ffa5
240f10021d083b0001000000000020000000044a4a000044000000040000
7ce6000d03baffffffffffffffff00ffa5240f10021d083b000100000000
0020000000044a4a0000440000000400007ce6000d03baff00ffa5006010
0401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff
00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa5
001060010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a
0202005b02ee000000000001152002baffffffffffffffff00ffa5240f10
021d083b0001000000000020000000044a4a0000440000000400007ce600
0d03baffffffffffffffff00ffa5240f1005080900041e06140000013aff
ffffffffffffff00ffa5240f10021d09000001000000000020000000044a
4a0000440000000400007ce6000d0380ff00ffa50060100401ff0219ff00
ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa500106006
010a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee
0208ff00ffa50060100700011cff00ffa5001060070f0a0202005a02ee00
0000000001152002b91002500000621003100200010000131003ffffffff
ffffffff00ffa5240f10021d09000001000000000020000000044a4a0000
440000000400007ce6000d0380ff00ffa50060100401ff0219ff00ffa500
10600401ff0219ffff00ffa50060100401ff0219ff00ffa50010600401ff
0219ff00ffa500601006010a0126ff00ffa500106006010a0126ff00ffa5
006010010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa50060
100700011cff00ffa5001060070f0a0202005c02ee000000000001152002
bbffffffffffffffff00ffa5240f10021d09000001000000000020000000
044a4a0000440000000400007ce6000d0380ffffffffffffffff00ffa524
0f10021d09000001000000000020000000044a4a0000440000000400007c
e6000d0380ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff
00ffa500601006010a0126ff00ffa500106006010a0126ff00ffa5006010
010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa50060100700
011cff00ffa5001060070f0a0202005c02ee000000000001152002bbffff
ffffffffffff00ffa5240f10021d09000001000000000020000000044a4a
0000440000000400007ce6000d0380ffffffffffffffff00ffa5240f1002
1d09000001000000000020000000044a4a0000440000000400007ce6000d
0380ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa5
00601006010a0126ff00ffa500106006010a0126ff00ffa5006010010402
c402ee02d0ff00ffa5001060010202ee0208ff00ffa50060100700011cff
00ffa5001060070f0a0202005b02ee000000000001152002baffffffffff
ffffff00ffa5240f10021d09000001000000000020000000044a4a000044
0000000400007ce6000d0380ffffffffffffffff00ffa5240f10021d0900
0001000000000020000000044a4a0000440000000400007ce6000d0380ff
00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa5006010
06010a0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee
02d0ff00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa5
001060070f0a0202005b02ee000000000001152002baffffffffffffffff
00ffa5240f10021d09000001000000000020000000044a4a000044000000
0400007ce6000d0380ffffffffffffffff00ffa5240f10021d0900000100
0000000020000000044a4a0000440000000400007ce6000d0380ff00ffa5
0060100401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a
0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff
00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa5001060
070f0a0202005c02ee000000000001152002bbffffffffffffffff00ffa5
240f10021d09000001000000000020000000044a4a000044000000040000
7ce6000d0380ffffffffffffffff00ffa5240f10021d0900000100000000
0020000000044a4a0000440000000400007ce6000d0380ffffffffffffff
ff00ffa5240f10021d09000001000000000020000000044a4a0000440000
000400007ce6000d0380ffffffffffffffff00ffa5240f10021d09000001
000000000020000000044a4a0000440000000400007ce6000d0380ffffff
ffffffffff00ffa5240f10021d09000001000000000020000000044a4a00
00440000000400007ce6000d0380ffffffffffffffff00ffa5240f10021d
09000001000000000020000000044a4a0000440000000400007ce6000d03
80ffffffffffffffff00ffa5240f10021d09000001000000000020000000
044a4a0000440000000400007ce6000d0380100250110073100310020012
4c81f11003ffffffffffffffff00ffa5240f10021d090000010000000000
20000000044a4a0000440000000400007ce6000d0380ff00ffa500601004
01ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00
ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa500
1060010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a02
02005a02ee000000000001152002b9ffffffffffffffff00ffa5240f1002
1d09000001000000000020000000044a4a0000440000000400007ce6000d
0380ffffffffffffffff00ffa5240f10021d090000010000000000200000
00044a4a0000440000000400007ce6000d0380ff00ffa50060100401ff02
19ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa500
106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa500106001
0202ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202005a
02ee000000000001152102baffffffffffffffff00ffa5240f10021d0900
0001000000000020000000044a4a0000440000000400007ce6000d0380ff
ffffffffffffff00ffa5240f10021d09000001000000000020000000044a
4a0000440000000400007ce6000d0380ff00ffa50060100401ff0219ff00
ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa500106006
010a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee
0208ff00ffa50060100700011cff00ffa5001060070f0a0202005b02ee00
0000000001152102bbffffffffffffffff00ffa5240f10021d0900000100
0000000020000000044a4a0000440000000400007ce6000d0380ffffffff
ffffffff00ffa5240f10fc110002500000010a0000000000000000000002
52ffffffffffffffff00ffa5240f10080d4a4a444e5e0400000000000000
0285ffffffffffffffff00ffa5240f1005080900041e06140000013affff
ffffffffffff00ffa5240f100b0501014800000142ffffffffffffffff00
ffa5240f100b050200110000010bffffffffffffffff00ffa5240f100b05
03005600000151ffffffffffffffff00ffa5240f100b0504104a00000156
ffffffffffffffff00ffa5240f100b0505103f0000014cffffffffffffff
ff00ffa5240f100b0506023d0000013dffffffffffffffff00ffa5240f10
0b0507000700000106ffffffffffffffff00ffa5240f100b050800080000
0108ffffffffffffffff00ffa5240f100b0509003000000131ffffffffff
ffffff00ffa5240f100b050a000000000102ffffffffffffffff00ffa524
0f100b050b05160000011effffffffffffffff00ffa5240f100b050c005f
00000163ffffffffffffffff00ffa5240f100b050d006000000165ffffff
ffffffffff00ffa5240f100b050e006100000167ffffffffffffffff00ff
a5240f100b050f006200000169ffffffffffffffff00ffa5240f100b0510
00630000016bffffffffffffffff00ffa5240f100b051100640000016dff
ffffffffffffff00ffa5240f100b05120fc8000001e1ffffffffffffffff
00ffa5240f1011070106010006007f018dffffffffffffffff00ffa5240f
101107020907010e007f01a0ffffffffffffffff00ffa5240f1011070309
110113007f01b0ffffffffffffffff00ffa5240f1011070409160100007f
01a3ffffffffffffffff00ffa5240f10110705030c000c287f01c7ffffff
ffffffffff00ffa5240f101107060000000000000106ffffffffffffffff
00ffa5240f101107070b0f0011007f01b1ffffffffffffffff00ffa5240f
101107080000000000000108ffffffffffffffff00ffa5240f1011070909
080011007f01aaffffffffffffffff00ffa5240f10272005040200041402
0000000a0000000a0000000a0000000a0000000a0000000a000190ffffff
ffffffffff00ffa5240f101d18030000001200ffffff0101020304050607
08090a010203040471ffffffffffffffff00ffa5240f10fc110002500000
010a000000000000000000000252ffffffffffffff
ff00ffa5240f10021d09000001000000000020000000044a4a0000440000000400007ce6000d0380
ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa500
601006010a0126ff00ffa500106006010a0126ff00ffa5006010010402c4
02ee02d0ff00ffa5001060010202ee0208ff00ffa50060100700011cff00
ffa5001060070f0a0202005b02ee000000000001152102bbffffffffffff
ffff00ffa5240f10021d09000001000000000020000000044a4a00004400
00000400007ce6000d0380ffffffffffffffff00ffa5240f10021d090000
01000000000020000000044a4a0000440000000400007ce6000d0380ff00
ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa500601006
010a0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee02
d0ff00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa500
1060070f0a0202005b02ee000000000001152102bb100250110073100310
0200124c81f11003ff00ffa50021600401ff022aff00ffa5002160070f0a
0202005d02ee000000000001152102ceffffffffffffffff00ffa5240f10
021d09000001000000000020000000044a4a0000440000000400007ce600
0d0380ff00ffa50060100401ff0219ff00ffa50010600401ff0219ffff00
ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa500601006
010a0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee02
d0ff00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa500
1060070f0a0202005c02ee000000000001152102bcffffffffffffffff00
ffa5240f10021d09000001000000000020000000044a4a00004400000004
00007ce6000d0380ffffffffffffffff00ffa5240f10021d090000010000
00000020000000044a4a0000440000000400007ce6000d0380ff00ffa500
60100401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a01
26ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff00
ffa5001060010202ee0208ff00ffa50060100700011cff00ffa500106007
0f0a0202005b02ee000000000001152102bbffffffffffffffff00ffa524
0f10021d09000001000000000020000000044a4a0000440000000400007c
e6000d0380ffffffffffffffff00ffa5240f10021d090000010000000000
20000000044a4a0000440000000400007ce6000d0380ff00ffa500601004
01ff0219ff00ffa50010600401ff0219ffff00ffa50060100401ff0219ff
00ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa5001060
06010a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202
ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202005b02ee
000000000001152102bbffffffffffffffff00ffa5240f10021d09000001
000000000020000000044a4a0000440000000400007ce6000d0380ffffff
ffffffffff00ffa5240f10021d09000001000000000020000000044a4a00
00440000000400007ce6000d0380ff00ffa50060100401ff0219ff00ffa5
0010600401ff0219ff00ffa500601006010a0126ff00ffa500106006010a
0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208
ff00ffa50060100700011cff00ffa5001060070f0a0202005c02ee000000
000001152102bcffffffffffffffff00ffa5240f10021d09000001000000
000020000000044a4a0000440000000400007ce6000d0380ffffffffffff
ffff00ffa5240f10080d4a4a444e5e04000000000000000285ffffffffff
ffffff00ffa5240f10021d09010001000000000020000000044a4a000044
0000000400007ce6000d0381ff00ffa50060100401ff0219ff00ffa50010
600401ff0219ff00ffa500601006010a0126ff00ffa500106006010a0126
ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208ff00
ffa50060100700011cff00ffa5001060070f0a0202005b02ee0000000000
01152102bb10025014007610031002000300496e74656c6c6963686c6f72
2d2d3430bc1003ffffffffffffffff00ffa5240f10021d09010001000000
000020000000044a4a0000440000000400007ce6000d0381ff00ffa50060
100401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126
ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff00ff
a5001060010202ee0208ff00ffa50060100700011cff00ffa5001060070f
0a0202005a02ee000000000001152102baffffffffffffffff00ffa5240f
10021d09010001000000000020000000044a4a0000440000000400007ce6
000d0381ffffffffffffffff00ffa5240f10021d09010001000000000020
000000044a4a0000440000000400007ce6000d0381ff00ffa50060100401
ff0219ff00ffa50010600401ff0219ffff00ffa50060100401ff0219ff00
ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa500106006
010a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee
0208ff00ffa50060100700011cff00ffa5001060070f0a0202005c02ee00
0000000001152102bcffffffffffffffff00ffa5240f10021d0901000100
0000000020000000044a4a0000440000000400007ce6000d0381ffffffff
ffffffff00ffa5240f10021d09010001000000000020000000044a4a0000
440000000400007ce6000d0381ff00ffa50021600401ff022aff00ffa500
2160070f0a0202005b02ee000000000001152102ccff00ffa50060100401
ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00ff
a500106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa50010
60010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202
005b02ee000000000001152102bbffffffffffffffff00ffa5240f10021d
09010001000000000020000000044a4a0000440000000400007ce6000d03
81ffffffffffffffff00ffa5240f10021d09010001000000000020000000
044a4a0000440000000400007ce6000d0381ff00ffa50060100401ff0219
ff00ffa50010600401ff0219ffff00ffa50060100401ff0219ff00ffa500
10600401ff0219ff00ffa500601006010a0126ff00ffa500106006010a01
26ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208ff
00ffa50060100700011cff00ffa5001060070f0a0202005b02ee00000000
0001152102bbffffffffffffffff00ffa5240f10021d0901000100000000
0020000000044a4a0000440000000400007ce6000d0381ffffffffffffff
ff00ffa5240f10021d09010001000000000020000000044a4a0000440000
000400007ce6000d0381ff00ffa50060100401ff0219ff00ffa500106004
01ff0219ff00ffa500601006010a0126ff00ffa500106006010a0126ff00
ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa5
0060100700011cff00ffa5001060070f0a0202005b02ee00000000000115
2102bbffffffffffffffff00ffa5240f10021d0901000100000000002000
0000044a4a0000440000000400007ce6000d0381ffffffffffffffff00ff
a5240f10021d09010001000000000020000000044a4a0000440000000400
007ce6000d0381ffffffffffffffff00ffa5240f10021d09010001000000
000020000000044a4a0000440000000400007ce6000d0381ffffffffffff
ffff00ffa5240f10021d09010001000000000020000000044a4a00004400
00000400007ce6000d0381ffffffffffffffff00ffa5240f10021d090100
01000000000020000000044a4a0000440000000400007ce6000d0381ffff
ffffffffffff00ffa5240f10021d09010001000000000020000000044a4a
0000440000000400007ce6000d0381ffffffffffffffff00ffa5240f1002
1d09010001000000000020000000044a4a0000440000000400007ce6000d
03811002501100731003100200124c81f11003ffffffffffffffff00ffa5
240f10021d09010001000000000020000000044a4a000044000000040000
7ce6000d0381ff00ffa50060100401ff0219ff00ffa50010600401ff0219
ff00ffa500601006010a0126ff00ffa500106006010a0126ff00ffa50060
10010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa500601007
00011cff00ffa5001060070f0a0202005b02ee000000000001152102bbff
ffffffffffffff00ffa5240f10021d09010001000000000020000000044a
4a0000440000000400007ce6000d0381ffffffffffffffff00ffa5240f10
021d09010001000000000020000000044a4a0000440000000400007ce600
0d0381ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ff
a500601006010a0126ff00ffa500106006010a0126ff00ffa50060100104
02c402ee02d0ff00ffa5001060010202ee0208ff00ffa50060100700011c
ff00ffa5001060070f0a0202005a02ee000000000001152202bbffffffff
ffffffff00ffa5240f10021d09010001000000000020000000044a4a0000
440000000400007ce6000d0381ffffffffffffffff00ffa5240f10021d09
010001000000000020000000044a4a0000440000000400007ce6000d0381
ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa50060
1006010a0126ff00ffa500106006010a0126ff00ffa5006010010402c402
ee02d0ff00ffa5001060010202ee0208ff00ffa50060100700011cff00ff
a5001060070f0a0202005b02ee000000000001152202bcffffffffffffff
ff00ffa5240f10021d09010001000000000020000000044a4a0000440000
000400007ce6000d0381ffffffffffffffff00ffa5240f10021d09010001
000000000020000000044a4a0000440000000400007ce6000d0381ff00ff
a50060100401ff0219ff00ffa50010600401ff0219ff00ffa50060100601
0a0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0
ff00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa50010
60070f0a0202005c02ee000000000001152202bdffffffffffffffff00ff
a5240f10021d09010001000000000020000000044a4a0000440000000400
007ce6000d0381ffffffffffffffff00ffa5240f10021d09010001000000
000020000000044a4a0000440000000400007ce6000d0381ff00ffa50060
100401ff0219ff00ffa50010600401ff0219ff00ffa50060100401ff0219
ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa50010
6006010a0126ff00ffa5006010010402c402ee02d0ff00ffa50010600102
02ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202005c02
ee000000000001152202bdff00ffa50021600401ff022aff00ffa5002160
070f0a0202005c02ee000000000001152202ce1002501100731003100200
124c81f11003ffffffffffffffff00ffa5240f10021d0901000100000000
0020000000044a4a0000440000000400007ce6000d0381ff00ffa5006010
0401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff
00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa5
001060010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a
0202005b02ee000000000001152202bcffffffffffffffff00ffa5240f10
021d09010001000000000020000000044a4a0000440000000400007ce600
0d0381ffffffffffffffff00ffa5240f10021d0901000100000000002000
0000044a4a0000440000000400007ce6000d0381ff00ffa50060100401ff
0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa5
00106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060
010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a020200
5a02ee000000000001152202bbffffffffffffffff00ffa5240f10021d09
010001000000000020000000044a4a0000440000000400007ce6000d0381
ffffffffffffffff00ffa5240f10021d0901000100000000002000000004
4a4a0000440000000400007ce6000d0381ff00ffa50060100401ff0219ff
00ffa50010600401ff0219ffff00ffa50060100401ff0219ff00ffa50010
600401ff0219ff00ffa500601006010a0126ff00ffa500106006010a0126
ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208ff00
ffa50060100700011cff00ffa5001060070f0a0202005b02ee0000000000
01152202bcffffffffffffffff00ffa5240f10021d090100010000000000
20000000044a4a0000440000000400007ce6000d0381ffffffffffffffff
00ffa5240f10021d09010001000000000020000000044a4a000044000000
0400007ce6000d0381ff00ffa50060100401ff0219ff00ffa50010600401
ff0219ff00ffa500601006010a0126ff00ffa500106006010a0126ff00ff
a5006010010402c402ee02d0ff00ffa5001060010202ee0208ffffffffff
ffffff00ffa5240f1005080902041e06140000013cff00ffa50060100700
011cff00ffa5001060070f0a0202005b02ee000000000001152202bcffff
ffffffffffff00ffa5240f10021d09020001000000000020000000044a4a
0000440000000400007ce6000d0382ffffffffffffffff00ffa5240f1002
1d09020001000000000020000000044a4a0000440000000400007ce6000d
0382ff00ffa50060100401ff0219ff00ffa50010600401ff0219ffff00ff
a50060100401ff0219ff00ffa50010600401ff0219ff00ffa50060100601
0a0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0
ff00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa50010
60070f0a0202005c02ee000000000001152202bd10025000006210031002
00010000131003ffffffffffffffff00ffa5240f10021d09020001000000
000020000000044a4a0000440000000400007ce6000d0382ff00ffa50060
100401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126
ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff00ff
a5001060010202ee0208ff00ffa50060100700011cff00ffa5001060070f
0a0202005b02ee000000000001152202bcffffffffffffffff00ffa5240f
10021d09020001000000000020000000044a4a0000440000000400007ce6
000d0382ffffffffffffffff00ffa5240f10021d09020001000000000020
000000044a4a0000440000000400007ce6000d0382ff00ffa50060100401
ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00ff
a500106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa50010
60010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202
005a02ee000000000001152202bbffffffffffffffff00ffa5240f10021d
09020001000000000020000000044a4a0000440000000400007ce6000d03
82ff00ffa50021600401ff022aff00ffa5002160070f0a0202005b02ee00
0000000001152202cdffffffffffffffff00ffa5240f10021d0902000100
0000000020000000044a4a0000440000000400007ce6000d0382ff00ffa5
0060100401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a
0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff
00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa5001060
070f0a0202005c02ee000000000001152202bdffffffffffffffff00ffa5
240f10021d09020001000000000020000000044a4a000044000000040000
7ce6000d0382ffffffffffffffff00ffa5240f10021d0902000100000000
0020000000044a4a0000440000000400007ce6000d0382ff00ffa5006010
0401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff
00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa5
001060010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a
0202005902ee000000000001152202baffffffffffffffff00ffa5240f10
021d09020001000000000020000000044a4a0000440000000400007ce600
0d0382ffffffffffffffff00ffa5240f10021d0902000100000000002000
0000044a4a0000440000000400007ce6000d0382ff00ffa50060100401ff
0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa5
00106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060
010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a020200
5d02ee000000000001152202beffffffffffffffff00ffa5240f10021d09
020001000000000020000000044a4a0000440000000400007ce6000d0382
ffffffffffffffff00ffa5240f10021d0902000100000000002000000004
4a4a0000440000000400007ce6000d0382ffffffffffffffff00ffa5240f
10021d09020001000000000020000000044a4a0000440000000400007ce6
000d0382ffffffffffffffff00ffa5240f10021d09020001000000000020
000000044a4a0000440000000400007ce6000d0382ffffffffffffffff00
ffa5240f10021d09020001000000000020000000044a4a00004400000004
00007ce6000d0382ffffffffffffffff00ffa5240f10021d090200010000
00000020000000044a4a0000440000000400007ce6000d0382ffffffffff
ffffff00ffa5240f10021d09020001000000000020000000044a4a000044
0000000400007ce6000d03821002501100731003100200124c81f11003ff
ffffffffffffff00ffa5240f10021d09020001000000000020000000044a
4a0000440000000400007ce6000d0382ff00ffa50060100401ff0219ff00
ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa500106006
010a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee
0208ff00ffa50060100700011cff00ffa5001060070f0a0202005b02ee00
0000000001152202bcffffffffffffffff00ffa5240f10021d0902000100
0000000020000000044a4a0000440000000400007ce6000d0382ffffffff
ffffffff00ffa5240f10021d09020001000000000020000000044a4a0000
440000000400007ce6000d0382ff00ffa50060100401ff0219ff00ffa500
10600401ff0219ff00ffa50060100401ff0219ff00ffa50010600401ff02
19ff00ffa500601006010a0126ff00ffa500106006010a0126ff00ffa500
6010010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa5006010
0700011cff00ffa5001060070f0a0202005b02ee000000000001152302bd
ffffffffffffffff00ffa5240f10021d0902000100000000002000000004
4a4a0000440000000400007ce6000d0382ffffffffffffffff00ffa5240f
10021d09020001000000000020000000044a4a0000440000000400007ce6
000d0382ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00
ffa500601006010a0126ff00ffa500106006010a0126ff00ffa500601001
0402c402ee02d0ff00ffa5001060010202ee0208ff00ffa5006010070001
1cff00ffa5001060070f0a0202005c02ee000000000001152302beffffff
ffffffffff00ffa5240f10021d09020001000000000020000000044a4a00
00440000000400007ce6000d0382ffffffffffffffff00ffa5240f10021d
09020001000000000020000000044a4a0000440000000400007ce6000d03
82ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa500
601006010a0126ff00ffa500106006010a0126ff00ffa5006010010402c4
02ee02d0ff00ffa5001060010202ee0208ff00ffa50060100700011cff00
ffa5001060070f0a0202005a02ee000000000001152302bcffffffffffff
ffff00ffa5240f10021d09020001000000000020000000044a4a00004400
00000400007ce6000d0382ffffffffffffffff00ffa5240f10021d090200
01000000000020000000044a4a0000440000000400007ce6000d0382ff00
ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa500601006
010a0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee02
d0ff00ffa5001060010202ee0208ff00ffa50021600401ff022aff00ffa5
002160070f0a0202005b02ee000000000001152302ceff00ffa500601007
00011cff00ffa5001060070f0a0202005c02ee000000000001152302be10
02501100731003100200124c81f11003ffffffffffffffff00ffa5240f10
021d09020001000000000020000000044a4a0000440000000400007ce600
0d0382ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ff
a500601006010a0126ff00ffa500106006010a0126ff00ffa50060100601
0a0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0
ff00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa50010
60070f0a0202005b02ee000000000001152302bdffffffffffffffff00ff
a5240f10021d09020001000000000020000000044a4a0000440000000400
007ce6000d0382ffffffffffffffff00ffa5240f10021d09020001000000
000020000000044a4a0000440000000400007ce6000d0382ff00ffa50060
100401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126
ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff00ff
a5001060010202ee0208ff00ffa50060100700011cff00ffa5001060070f
0a0202005a02ee000000000001152302bcffffffffffffffff00ffa5240f
10021d09020001000000000020000000044a4a0000440000000400007ce6
000d0382ffffffffffffffff00ffa5240f10021d09020001000000000020
000000044a4a0000440000000400007ce6000d0382ff00ffa50060100401
ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00ff
a500106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa50010
60010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202
005b02ee000000000001152302bdffffffffffffffff00ffa5240f10021d
09020001000000000020000000044a4a0000440000000400007ce6000d03
82ffffffffffffffff00ffa5240f10021d09020001000000000020000000
044a4a0000440000000400007ce6000d0382ff00ffa50060100401ff0219
ff00ffa50010600401ff0219ffff00ffa500601006010a0126ff00ffa500
106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa500106001
0202ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202005b
02ee000000000001152302bdffffffffffffffff00ffa5240f10021d0903
0001000000000020000000044a4a0000440000000400007ce6000d0383ff
ffffffffffffff00ffa5240f10021d09030001000000000020000000044a
4a0000440000000400007ce6000d0383ff00ffa50060100401ff0219ff00
ffa50010600401ff0219ff00ffa50060100401ff0219ff00ffa500106004
01ff0219ff00ffa500601006010a0126ff00ffa500106006010a0126ff00
ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa5
0060100700011cff00ffa5001060070f0a0202005d02ee00000000000115
2302bf10025014007610031002000300496e74656c6c6963686c6f722d2d
3430bc1003ffffffffffffffff00ffa5240f10021d090300010000000000
20000000044a4a0000440000000400007ce6000d0383ff00ffa500601004
01ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00
ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa500
1060010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a02
02005c02ee000000000001152302beffffffffffffffff00ffa5240f1002
1d09030001000000000020000000044a4a0000440000000400007ce6000d
0383ffffffffffffffff00ffa5240f10021d090300010000000000200000
00044a4a0000440000000400007ce6000d0383ff00ffa50060100401ff02
19ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa500
106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa500106001
0202ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202005c
02ee000000000001152302beffffffffffffffff00ffa5240f10021d0903
0001000000000020000000044a4a0000440000000400007ce6000d0383ff
00ffa50021600401ff022aff00ffa5002160070f0a0202005b02ee000000
000001152302ceffffffffffffffff00ffa5240f10021d09030001000000
000020000000044a4a0000440000000400007ce6000d0383ff00ffa50060
100401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126
ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff00ff
a5001060010202ee0208ff00ffa50060100700011cff00ffa5001060070f
0a0202005c02ee000000000001152302beffffffffffffffff00ffa5240f
10021d09030001000000000020000000044a4a0000440000000400007ce6
000d0383ffffffffffffffff00ffa5240f10021d09030001000000000020
000000044a4a0000440000000400007ce6000d0383ff00ffa50060100401
ff0219ff00ffa50010600401ff0219ff00ffa50060100401ff0219ff00ff
a50010600401ff0219ff00ffa500601006010a0126ff00ffa50010600601
0a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee02
08ff00ffa50060100700011cff00ffa5001060070f0a0202005b02ee0000
00000001152302bdffffffffffffffff00ffa5240f10021d090300010000
00000020000000044a4a0000440000000400007ce6000d0383ffffffffff
ffffff00ffa5240f10021d09030001000000000020000000044a4a000044
0000000400007ce6000d0383ff00ffa50060100401ff0219ff00ffa50010
600401ff0219ff00ffa500601006010a0126ff00ffa500106006010a0126
ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208ff00
ffa50060100700011cff00ffa5001060070f0a0202005c02ee0000000000
01152302beffffffffffffffff00ffa5240f10021d090300010000000000
20000000044a4a0000440000000400007ce6000d0383ffffffffffffffff
00ffa5240f10021d09030001000000000020000000044a4a000044000000
0400007ce6000d0383ffffffffffffffff00ffa5240f10021d0903000100
0000000020000000044a4a0000440000000400007ce6000d0383ffffffff
ffffffff00ffa5240f10021d09030001000000000020000000044a4a0000
440000000400007ce6000d0383ffffffffffffffff00ffa5240f10021d09
030001000000000020000000044a4a0000440000000400007ce6000d0383
ffffffffffffffff00ffa5240f10021d0903000100000000002000000004
4a4a0000440000000400007ce6000d0383ffffffffffffffff00ffa5240f
10021d09030001000000000020000000044a4a0000440000000400007ce6
000d03831002501100731003100200124c81f11003ffffffffffffffff00
ffa5240f10021d09030001000000000020000000044a4a00004400000004
00007ce6000d0383ff00ffa50060100401ff0219ff00ffa50010600401ff
0219ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa5
00601006010a0126ff00ffa500106006010a0126ff00ffa5006010010402
c402ee02d0ff00ffa5001060010202ee0208ff00ffa50060100700011cff
00ffa5001060070f0a0202005c02ee000000000001152302beffffffffff
ffffff00ffa5240f10021d09030001000000000020000000044a4a000044
0000000400007ce6000d0383ffffffffffffffff00ffa5240f10021d0903
0001000000000020000000044a4a0000440000000400007ce6000d0383ff
00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa5006010
06010a0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee
02d0ff00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa5
001060070f0a0202005b02ee000000000001152402beffffffffffffffff
00ffa5240f10021d09030001000000000020000000044a4a000044000000
0400007ce6000d0383ffffffffffffffff00ffa52421100101850181ffff
ffffffffffff00ffa5240f10021d09030001000000000020000000044a4a
0000450000000400007ce6000d0384ff00ffa50060100401ff0219ff00ff
a50010600401ff0219ff00ffa50060100401ff0219ff00ffa50010600401
ff0219ff00ffa500601006010a0126ff00ffa500106006010a0126ff00ff
a5006010010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa500
60100700011cff00ffa5001060070f0a0202005d02ee0000000000011524
02c0ffffffffffffffff00ffa5240f10021d090300010000000000200000
00044a4a0000450000000400007ce6000d0384ffffffffffffffff00ffa5
240f10021d09030001000000000020000000044a4a000045000000040000
7ce6000d0384ff00ffa50060100401ff0219ff00ffa50010600401ff0219
ff00ffa500601006010a0126ff00ffa500106006010a0126ff00ffa50060
10010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa500601007
00011cff00ffa5001060070f0a0202005b02ee000000000001152402beff
ffffffffffffff00ffa5240f10021d09030001000000000020000000044a
4a0000450000000400007ce6000d0384ffffffffffffff
ff00ffa5240f10021d09030001000000000020000000044a4a0000450000000400007ce6000dfffc
ff00ffa50060100401ff021900ffa50010600401ff0219ff00ffa5
0060100401ff0219ff00ffa50010600401ff0219ff00ffa50060100401ff
0219ff00ffa50010600401ff0219ff00ffa50060100700011cff00ffa500
1060070f0a0202005c02ee000000000001152402bfff00ffa50060100700
011cff00ffa5001060070f0a0202005c02ee000000000001152402bf1002
501100731003100200124c81f11003ffffffffffffffff00ffa5240f1002
1d09030001000000000020000000044a4a0000440000000400007ce6000d
0383ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa5
0060100401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a
0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff
00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa5001060
070f0a0202005b02ee000000000001152402beffffffffffffffff00ffa5
240f10021d09030001000000000020000000044a4a000044000000040000
7ce6000d0383ffffffffffffffff00ffa5240f10021d0903000100000000
0020000000044a4a0000440000000400007ce6000d0383ff00ffa5006010
0401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff
00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa5
001060010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a
0202005b02ee000000000001152402beffffffffffffffff00ffa5240f10
021d09030001000000000020000000044a4a0000440000000400007ce600
0d0383ffffffffffffffff00ffa5240f10021d0903000100000000002000
0000044a4a0000440000000400007ce6000d0383ff00ffa50060100401ff
0219ff00ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa5
00106006010a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060
010202ee0208ff00ffa50060100700011cff00ffa5001060070f0a020200
5b02ee000000000001152402beffffffffffffffff00ffa5240f10021d09
030001000000000020000000044a4a0000440000000400007ce6000d0383
ffffffffffffffff00ffa5240f10021d0903000100000000002000000004
4a4a0000440000000400007ce6000d0383ff00ffa50060100401ff0219ff
00ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa5001060
06010a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202
ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202005b02ee
000000000001152402beffffffffffffffff00ffa5240f10021d09030001
000000000020000000044a4a0000440000000400007ce6000d0383ffffff
ffffffffff00ffa5240f10021d09030001000000000020000000044a4a00
00440000000400007ce6000d0383ff00ffa50060100401ff0219ff00ffa5
0010600401ff0219ff00ffa500601006010a0126ff00ffa500106006010a
0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208
ff00ffa50060100700011cff00ffa5001060070f0a0202005d02ee000000
000001152402c01002501100731003100200124c81f11003ffffffffffff
ffff00ffa5240f10021d09030001000000000020000000044a4a00004500
00000400007ce6000d0384ff00ffa50060100401ff0219ff00ffa5001060
0401ff0219ff00ffa500601006010a0126ff00ffa500106006010a0126ff
00ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208ff00ff
a50060100700011cff00ffa5001060070f0a0202005b02ee000000000001
152402beffffffffffffffff00ffa5240f10021d09030001000000000020
000000044a4a0000450000000400007ce6000d0384ffffffffffffffff00
ffa5240f10021d09030001000000000020000000044a4a00004500000004
00007ce6000d0384ff00ffa50060100401ff0219ff00ffa50010600401ff
0219ffff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ff
a500601006010a0126ff00ffa500106006010a0126ff00ffa50060100104
02c402ee02d0ff00ffa5001060010202ee0208ff00ffa50060100700011c
ff00ffa5001060070f0a0202005b02ee000000000001152402beffffffff
ffffffff00ffa5240f10021d09030001000000000020000000044a4a0000
450000000400007ce6000d0384ff00ffa50021600401ff022aff00ffa500
2160070f0a0202005c02ee000000000001152402d0ffffffffffffffff00
ffa5240f10021d09030001000000000020000000044a4a00004500000004
00007ce6000d0384ff00ffa50060100401ff0219ff00ffa50010600401ff
0219ff00ffa500601006010a0126ff00ffa500106006010a0126ff00ffa5
006010010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa50060
100700011cff00ffa5001060070f0a0202005b02ee000000000001152402
beffffffffffffffff00ffa5240f10021d09030001000000000020000000
044a4a0000450000000400007ce6000d0384ffffffffffffffff00ffa524
0f10021d09030001000000000020000000044a4a0000450000000400007c
e6000d0384ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff
00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa5006010
06010a0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee
02d0ff00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa5
001060070f0a0202005c02ee000000000001152402bfffffffffffffffff
00ffa5240f10021d09030001000000000020000000044a4a000045000000
0400007ce6000d0384ffffffffffffffff00ffa5240f10021d0903000100
0000000020000000044a4a0000450000000400007ce6000d0384ff00ffa5
0060100401ff0219ff00ffa50010600401ff0219ff00ffa500601006010a
0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0ff
00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa5001060
070f0a0202005b02ee000000000001152402beffffffffffffffff00ffa5
240f10021d09030001000000000020000000044a4a000045000000040000
7ce6000d0384ffffffffffffffff00ffa5240f10021d0903000100000000
0020000000044a4a0000450000000400007ce6000d0384ffffffffffffff
ff00ffa5240f10021d09030001000000000020000000044a4a0000450000
000400007ce6000d0384ffffffffffffffff00ffa5240f10021d09030001
000000000020000000044a4a0000450000000400007ce6000d0384ffffff
ffffffffff00ffa5240f10021d09030001000000000020000000044a4a00
00450000000400007ce6000d0384ffffffffffffffff00ffa5240f10021d
09030001000000000020000000044a4a0000450000000400007ce6000d03
84ffffffffffffffff00ffa5240f10021d09030001000000000020000000
044a4a0000450000000400007ce6000d0384100250110073100310020012
4c81f11003ffffffffffffffff00ffa5240f10021d090300010000000000
20000000044a4a0000450000000400007ce6000d0384ff00ffa500601004
01ff0219ff00ffa50010600401ff0219ff00ffa50060100401ff0219ff00
ffa50010600401ff0219ff00ffa500601006010a0126ff00ffa500106006
010a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee
0208ff00ffa50060100700011cff00ffa5001060070f0a0202005c02ee00
0000000001152502c0ffffffffffffffff00ffa5240f10021d0903000100
0000000020000000044a4a0000450000000400007ce6000d0384ffffffff
ffffffff00ffa5240f10021d09030001000000000020000000044a4a0000
450000000400007ce6000d0384ff00ffa50060100401ff0219ff00ffa500
10600401ff0219ff00ffa500601006010a0126ff00ffa500106006010a01
26ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208ff
00ffa50060100700011cff00ffa5001060070f0a0202005b02ee00000000
0001152502bfffffffffffffffff00ffa5240f10021d0903000100000000
0020000000044a4a0000450000000400007ce6000d0384ffffffffffffff
ff00ffa5240f1005080904041e06140000013effffffffffffffff00ffa5
240f10021d09040001000000000020000000044a4a000045000000040000
7ce6000d0385ff00ffa50060100401ff0219ff00ffa50010600401ff0219
ff00ffa500601006010a0126ff00ffa500106006010a0126ff00ffa50060
10010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa500601007
00011cff00ffa5001060070f0a0202005b02ee000000000001152502bfff
ffffffffffffff00ffa5240f10021d09040001000000000020000000044a
4a0000450000000400007ce6000d0385ffffffffffffffff00ffa5240f10
021d09040001000000000020000000044a4a0000450000000400007ce600
0d0385ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ff
a500601006010a0126ff00ffa500106006010a0126ff00ffa50060100104
02c402ee02d0ff00ffa5001060010202ee0208ff00ffa50060100700011c
ff00ffa5001060070f0a0202005a02ee000000000001152502beffffffff
ffffffff00ffa5240f10021d09040001000000000020000000044a4a0000
450000000400007ce6000d0385ffffffffffffffff00ffa5240f10021d09
040001000000000020000000044a4a0000450000000400007ce6000d0385
ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa5ffff
1fffffeafffdb596500a0126ff00ffa500601006010a0126ff00ffa50010
6006010a0126ff00ffa5006010010402c402ee02d0ff00ffa50010600102
02ee0208ff00ffa50060100700011cff00ffa5001060070f0a0202005b02
ee000000000001152502bf1002500000621003100200010000131003ffff
ffffffffffff00ffa5240f10021d09040001000000000020000000044a4a
0000450000000400007ce6000d0385ff00ffa50060100401ff0219ff00ff
a50010600401ff0219ff00ffa50060100401ff0219ff00ffa50010600401
ff0219ff00ffa500601006010a0126ff00ffa500106006010a0126ff00ff
a5006010010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa500
60100700011cff00ffa5001060070f0a0202005d02ee0000000000011525
02c1ffffffffffffffff00ffa5240f10021d090400010000000000200000
00044a4a0000450000000400007ce6000d0385ffffffffffffffff00ffa5
240f10021d09040001000000000020000000044a4a000045000000040000
7ce6000d0385ff00ffa50060100401ff0219ff00ffa50010600401ff0219
ff00ffa500601006010a0126ff00ffa500106006010a0126ff00ffa50060
10010402c402ee02d0ff00ffa5001060010202ee0208ff00ffa500601007
00011cff00ffa5001060070f0a0202005b02ee000000000001152502bfff
ffffffffffffff00ffa5240f10021d09040001000000000020000000044a
4a0000450000000400007ce6000d0385ffffffffffffffff00ffa5240f10
021d09040001000000000020000000044a4a0000450000000400007ce600
0d0385ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ff
a50060100401ff0219ff00ffa50010600401ff0219ff00ffa50060100601
0a0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0
ff00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa50010
60070f0a0202005a02ee000000000001152502beffffffffffffffff00ff
a5240f10021d09040001000000000020000000044a4a0000450000000400
007ce6000d0385ffffffffffffffff00ffa5240f10021d09040001000000
000020000000044a4a0000450000000400007ce6000d0385ff00ffa50060
100401ff0219ff00ffa50010600401ff0219ff00
ffa500601006010a0126
ff00ffa500106006010a0126ff00ffa5006010010402c402ee02d0
ff00ffa5001060010202ee0208

View File

@ -0,0 +1,90 @@
ffffffffffffff
ff00ffa5240f10021d083a0001000000000020000000044a4a0000440000000400007ce6000d03b9
ff00ffa50060010401ff0219
ff00ffa50010600401ff0219
ff00ffa500601006010a0126
ff00ffa500106006010a0126
ff00ffa5006010010402c402ee02d0
ff00ffa5001060010202ee0208
ff00ffa50060100700011c
ff00ffa5001060070f0a0202005c02ee000000000001151f02ba
ffffffffffffff
ff00ffa5240f10021d083a0001000000000020000000044a4a0000440000000400007ce6000d03b9
ffffffffffffff
ff00ffa5240f10021d083a0001000000000020000000044a4a0000440000000400007ce6000d03b9
ff00ffa50060100401ff0219
ff00ffa50010600401ff0219
ff00ffa500601006010a0126
ff00ffa500106006010a0126
ff00ffa5006010010402c402ee02d0
ff00ffa5001060010202ee0208
ffffffffffffff
ff00ffa5240f10080d4a4a444e5e04000000000000000285
ff00ffa50060100700011c
ff00ffa5001060070f0a0202005a02ee000000000001151f02b8
1002501400761003
10 02 00 03 00 49 6e 74 65 6c 6c 69 63 68 6c 6f 72 2d 2d 34 30 bc 10 03
ffffffffffffff
ff00ffa5240f10021d083b0001000000000020000000044a4a0000440000000400007ce6000d03ba
ff00ffa50060100401ff0219
ff00ffa50010600401ff0219
ff00ffa50060100401ff0219
ff00ffa50010600401ff0219
ff00ffa500601006010a0126
ff00ffa500106006010a0126
ff00ffa5006010010402c402ee02d0
ff00ffa5001060010202ee0208
ff00ffa50060100700011c
ff00ffa5001060070f0a0202005c02ee000000000001151f02ba
ffffffffffffff
ff00ffa5240f10021d083b0001000000000020000000044a4a0000440000000400007ce6000d03ba
ffffffffffffff
ff00ffa5240f10021d083b0001000000000020000000044a4a0000440000000400007ce6000d03ba
ff00ffa50060100401ff0219
ff00ffa50010600401ff0219
ff00ffa500601006010a0126
ff00ffa500106006010a0126
ff00ffa5006010010402c402ee02d0
ff00ffa5001060010202ee0208
ff00ffa50060100700011c
ff00ffa5001060070f0a0202005a02ee000000000001151f02b8
ffffffffffffff
ff00ffa5240f10021d083b0001000000000020000000044a4a0000440000000400007ce6000d03ba
ffffffffffffff
ff00ffa5240f10021d083b0001000000000020000000044a4a
0000440000000400007ce6000d03baff00ffa50060100401ff0219ff00ff
a50010600401ff0219ff00ffa500601006010a0126ff00ffa50010600601
0a0126ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee02
08ff00ffa50060100700011cff00ffa5001060070f0a0202005b02ee0000
00000001151f02b9ffffffffffffffff00ffa5240f10021d083b00010000
00000020000000044a4a0000440000000400007ce6000d03baffffffffff
ffffff00ffa5240f10021d083b0001000000000020000000044a4a000044
0000000400007ce6000d03baff00ffa50060100401ff0219ff00ffa50010
600401ff0219ff00ffa500601006010a0126ff00ffa500106006010a0126
ff00ffa5006010010402c402ee02d0ff00ffa5001060010202ee0208ff00
ffa50060100700011cff00ffa5001060070f0a0202005b02ee0000000000
01151f02b9ffffffffffffffff00ffa5240f10021d083b00010000000000
20000000044a4a0000440000000400007ce6000d03baffffffffffffffff
00ffa5240f10021d083b0001000000000020000000044a4a000044000000
0400007ce6000d03baff00ffa50060100401ff0219ff00ffa50010600401
ff0219ff00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ff
a500601006010a0126ff00ffa500106006010a0126ff00ffa50060100104
02c402ee02d0ff00ffa5001060010202ee0208ff00ffa50060100700011c
ff00ffa5001060070f0a0202005c02ee000000000001151f02baffffffff
ffffffff00ffa5240f10021d083b0001000000000020000000044a4a0000
440000000400007ce6000d03baffffffffffffffff00ffa5240f10021d08
3b0001000000000020000000044a4a0000440000000400007ce6000d03ba
ffffffffffffffff00ffa5240f10021d083b000100000000002000000004
4a4a0000440000000400007ce6000d03baffffffffffffffff00ffa5240f
10021d083b0001000000000020000000044a4a0000440000000400007ce6
000d03baffffffffffffffff00ffa5240f10021d083b0001000000000020
000000044a4a0000440000000400007ce6000d03baffffffffffffffff00
ffa5240f10021d083b0001000000000020000000044a4a00004400000004
00007ce6000d03baffffffffffffffff00ffa5240f10021d083b00010000
00000020000000044a4a0000440000000400007ce6000d03ba1002501100
731003100200124c81f11003ffffffffffffffff00ffa5240f10021d083b
0001000000000020000000044a4a0000440000000400007ce6000d03baff
00ffa50060100401ff0219ff00ffa50010600401ff0219ff00ffa5006010
06010a0126ff00ffa500106006010a0126ff00ffa5006010010402c402ee
02d0ff00ffa5001060010202ee0208ff00ffa50060100700011cff00ffa5
001060070f0a0202005b02ee000000000001151f02b9ffffffffffffffff

View File

@ -0,0 +1,276 @@
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 02 1D 14 1E 00 00 00 00 00 00 00 00 03 00 40 04 39 39
20 00 3A 38 00 00 04 00 00 88 BE 00 0D 03 C7
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 02 1D 14 1E 00 00 00 00 00 00 00 00 03 00 40 04 39 39
20 00 3A 38 00 00 04 00 00 88 BE 00 0D 03 C7
FF 00 FF A5 10 10 22 86 02 0B 01 01 7B
FF FF FF FF FF FF FF FF 00 FF A5 10 22 10 01 01 86 01 6F
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 02 1D 14 1E 20 04 00 00 00 00 00 00 03 00 40 04 39 39
20 00 3A 38 00 00 04 00 00 88 BE 00 0D 03 EB
FF 00 FF A5 00 60 10 07 00 01 1C
FF 00 FF A5 00 10 60 07 0F 04 00 00 00 00 00 00 00 00 00 00 00 00 14 1E 01 61 FF 00 FF A5 00 61
10 07 00 01 1D
FF 00 FF A5 00 10 61 07 0F 04 00 00 00 00 00 00 00 00 00 00 00 00 14 1E 01 62
FF 00 FF A5 10 10 22 86 02 06 00 01 75
FF FF FF FF FF FF FF FF 00 FF A5 10 22 10 01 01 86 01 6F
10 02 50 11 00 73 10 03
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 02 1D 14 1E 20 04 00 00 00 00 00 00 03 00 40 04 39 39
20 00 3A 38 00 00 04 00 00 88 BE 00 0D 03 EB
FF 00 FF A5 00 60 10 04 01 FF 02 19
FF 00 FF A5 00 10 60 04 01 FF 02 19
FF 00 FF A5 00 60 10 06 01 0A 01 26
FF 00 FF A5 00 10 60 06 01 0A 01 26
FF 00 FF A5 00 60 10 01 04 02 C4 07 6C 02 53
FF 00 FF A5 00 10 60 01 02 07 6C 01 8B
FF 00 FF A5 00 61 10 04 01 FF 02 1A FF 00 FF A5 00 10 61 04 01 FF 02 1A
FF 00 FF A5 00 61 10 06 01 04 01 21
FF 00 FF A5 00 10 61 06 01 04 01 21
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 02 1D 14 1E 20 04 00 00 00 00 00 00 03 00 40 04 39 39
20 00 3A 38 00 00 04 00 00 88 BE 00 0D 03 EB
FF 00 FF A5 10 10 22 88 04 52 64 07 00 02 30
FF FF FF FF FF FF FF FF 00 FF A5 10 22 10 01 01 88 01 71
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 02 1D 14 1E 20 04 00 00 00 00 00 00 03 00 40 04 39 39
20 00 3A 38 00 00 07 00 00 7D C1 00 0D 03 E6 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 C5 01 00
01 AB FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 05 08 14 1E 04 07 02 11 00 01 01 32 FF FF FF FF
FF FF FF FF 00 FF A5 10 10 20 C8 01 00 01 AE
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 08 0D 39 39 3A 52 64 07 00 00 38 00 00 00 00 02 8A
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 00 01 B0
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 00 57 74 72 46 61 6C 6C 20 31 00 FB 04 F2 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 01 01 B1
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 01 57 74 72 46 61 6C 6C 20 31 2E 35 04 5B FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 02 01 B2
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 02 57 74 72 46 61 6C 6C 20 32 00 FB 04 F5 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 03 01 B3
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 03 57 74 72 46 61 6C 6C 20 33 00 FB 04 F7 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 04 01 B4
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 04 50 6F 6F 6C 20 4C 6F 77 32 00 FB 05 07 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 05 01 B5
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 05 55 53 45 52 4E 41 4D 45 2D 30 36 03 E2
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 06 01 B6
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 06 55 53 45 52 4E 41 4D 45 2D 30 37 03 E4 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 07 01 B7
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 07 55 53 45 52 4E 41 4D 45 2D 30 38 03 E6 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 08 01 B8
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 08 55 53 45 52 4E 41 4D 45 2D 30 39 03 E8 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 09 01 B9
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 09 55 53 45 52 4E 41 4D 45 2D 31 30 03 E1 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 01 01 B2
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 01 01 48 00 00 01 2E FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 02 01 B3
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 02 00 2E 00 00 01 14
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 03 01 B4
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 03 00 02 00 00 00 E9 FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 04 01 B5 FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 04 05 16 00 00 01
03 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 05 01 B6 FF FF FF FF FF FF FF FF 00 FF A5 10
0F 10 0B 05 05 40 C9 00 00 01 F2 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 06 01 B7 FF FF
FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 06 42 3D 00 00 01 69 FF FF FF FF FF FF FF FF 00 FF A5
10 10 20 CB 01 07 01 B8 FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 07 07 4A 00 00 01 3C FF
FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 08 01 B9 FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10
0B 05 08 07 3F 00 00 01 32 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 09 01 BA FF FF FF FF
FF FF FF FF 00 FF A5 10 0F 10 0B 05 09 07 37 00 00 01 2B FF FF FF FF FF FF FF FF 00 FF A5 10 10
20 CB 01 0A 01 BB FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 0A 00 00 00 00 00 EE FF FF FF
FF FF FF FF FF 00 FF A5 10 10 20 CB 01 0B 01 BC FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05
0B 0E 4F 00 00 01 4C FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 0C 01 BD FF FF FF FF FF FF
FF FF 00 FF A5 10 0F 10 0B 05 0C 00 C8 00 00 01 B8 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB
01 0D 01 BE
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 0D 00 CA 00 00 01 BB FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 0E 01 BF
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 0E 00 CB 00 00 01 BD FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 0F 01 C0
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 0F 00 CC 00 00 01 BF FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 10 01 C1
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 10 0E 35 00 00 01 37
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 11 01 C2
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 11 0E 35 00 00 01 38 FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 12 01 C3
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 12 0E 35 00 00 01 39 FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 D1 01 01 01 B8
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 01 06 09 19 0F 37 FF 02 5A
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D1 01 02 01 B9 FF FF FF FF FF FF FF FF 00 FF A5 10 0F
10 11 07 02 0D 0E 39 0F 08 D5 02 2E
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D1 01 03 01 BA
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 03 04 0A 0F 0B 00 FF 02 16 FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 D1 01 04 01 BB
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 04 06 19 00 07 0F 00 01 25 FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 D1 01 05 01 BC
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 05 04 19 00 04 00 00 01 12 FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 D1 01 06 01 BD
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 06 0F 15 0A 17 37 FF 02 6D
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D1 01 07 01 BE
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 07 0F 00 05 09 14 FF 02 23 FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 D1 01 08 01 BF FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 08 07 19 00
02 00 00 01 16 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D1 01 09 01 C0 FF FF FF FF FF FF FF FF
00 FF A5 10 0F 10 11 07 09 02 19 00 03 2D 00 01 40 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D1
01 0A 01 C1 FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 0A 09 19 00 04 0F 00 01 2B FF FF FF
FF FF FF FF FF 00 FF A5 10 10 20 D1 01 0B 01 C2 FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07
0B 0B 0D 00 0D 0B FF 02 26 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D1 01 0C 01 C3 FF FF FF FF
FF FF FF FF 00 FF A5 10 0F 10 11 07 0C 05 0D 14 0D 28 95 01 E8 FF FF FF FF FF FF FF FF 00 FF A5
10 10 20 E2 01 00 01 C8 FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 22 03 07 80 44 01 C4 FF FF FF
FF FF FF FF FF 00 FF A5 10 10 20 E3 01 00 01 C9 FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 23 02
10 00 01 09
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 E8 01 00 01 CE
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 28 0A 00 00 00 FE 01 00 00 00 00 00 02 05 FF FF FF FF
FF FF FF FF 00 FF A5 10 10 20 DE 01 00 01 C4
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 1E 10 00 00 00 00 01 48 00 00 00 2E 00 00 00 02 00 00
01 7B FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 E1 01 00 01 C7 FF FF FF FF FF FF FF FF 00 FF A5
10 0F 10 21 04 01 02 03 04 01 03 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 E0 01 00 01 C6
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 20 0B 00 07 02 01 08 05 06 07 08 09 0A 01 3E FF FF FF
FF FF FF FF FF 00 FF A5 10 10 20 DD 01 00 01 C3
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 1D 18 02 00 00 00 80 01 FF FF FF 00 07 02 01 08 05 06
07 08 09 0A 01 02 03 04 04 D2 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D9 01 00 01 BF
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 19 16 0B 08 80 1C 85 00 49 6E 74 65 6C 6C 69 63 68 6C
6F 72 2D 2D 34 30 07 DE FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D6 01 00 01 BC FF FF FF FF FF
FF FF FF 00 FF A5 10 0F 10 16 10 00 02 07 6C 00 01 32 0A 01 90 0D 7A 0F 82 00 00 03 55
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 E7 01 00 01 CD
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 27 20 08 00 00 00 09 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 2C FF FF FF FF FF FF FF FF 00 FF A5 10 10 20
E0 01 01 01 C7 FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 20 0B 01 01 02 03 04 05 06 07 08 09 0A
01 37 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D8 01 01 01 BF
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 18 1F 01 80 00 02 00 01 06 02 0C 04 09 0B 07 06 05 80
08 84 03 0F 03 03 D6 80 2E 6C 14 AC E8 20 E8 07 91
FF 00 FF A5 10 10 22 88 04 53 64 07 00 02 31
FF FF FF FF FF FF FF FF 00 FF A5 10 22 10 01 01 88 01 71
FF
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D8 01 02 01 C0
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 18 1F 02 80 03 02 00 0C 03 05 05 0D 07 0E 0B 00 03 00
03 00 03 00 03 0C E8 DC D0 B8 E8 E8 E8 E8 1C 08 F8
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 02 1D 14 1E 20 04 00 00 00 00 00 00 03 00 40 04 39 39
20 00 3A 38 00 00 07 00 00 81 C2 00 0D 03 EB
FF 00 FF A5 10 10 22 88 04 53 64 04 00 02 2E
FF FF FF FF FF FF FF FF 00 FF A5 10 22 10 01 01 88 01 71
FF 00 FF A5 00 60 10 04 01 FF 02 19
FF 00 FF A5 00 10 60 04 01 FF 02 19
FF 00 FF A5 00 60 10 06 01 0A 01 26
FF 00 FF A5 00 10 60 06 01 0A 01 26 FF 00 FF A5 00 60 10 01 04 02 C4 07 6C 02 53 FF 00 FF A5 00
10 60 01 02 07 6C 01 8B FF 00 FF A5 00 61 10 04 01 FF 02 1A FF 00 FF A5 00 10 61 04 01 FF 02 1A
FF 00 FF A5 00 61 10 06 01 04 01 21 FF 00 FF A5 00 10 61 06 01 04 01 21
FF 00 FF A5 10 10 22 86 02 0B 00 01 7A
FF FF FF FF FF FF FF FF 00 FF A5 10 22 10 01 01 86 01 6F
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 02 1D 14 1E 00 00 00 00 00 00 00 00 03 00 40 04 39 39
20 00 3A 38 00 00 04 00 00 82 BF 00 0D 03 C2 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 C5 01 00
01 AB
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 05 08 14 1E 04 07 02 11 00 01 01 32 FF FF FF FF FF FF
FF FF 00 FF A5 10 10 20 C8 01 00 01 AE FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 08 0D 39 39 3A
53 64 04 00 00 38 00 00 00 00 02 88
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 00 01 B0
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 00 57 74 72 46 61 6C 6C 20 31 00 FB 04 F2 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 01 01 B1
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 01 57 74 72 46 61 6C 6C 20 31 2E 35 04 5B FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 02 01 B2
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 02 57 74 72 46 61 6C 6C 20 32 00 FB 04 F5 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 03 01 B3
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 03 57 74 72 46 61 6C 6C 20 33 00 FB 04 F7 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 04 01 B4
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 04 50 6F 6F 6C 20 4C 6F 77 32 00 FB 05 07
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 05 01 B5
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 05 55 53 45 52 4E 41 4D 45 2D 30 36 03 E2 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 06 01 B6
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 06 55 53 45 52 4E 41 4D 45 2D 30 37 03 E4 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 07 01 B7
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 07 55 53 45 52 4E 41 4D 45 2D 30 38 03 E6 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 08 01 B8
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A 0C 08 55 53 45 52 4E 41 4D 45 2D 30 39 03 E8 FF FF
FF FF FF FF FF FF 00 FF A5 10 10 20 CA 01 09 01 B9 FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0A
0C 09 55 53 45 52 4E 41 4D 45 2D 31 30 03 E1 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 01
01 B2 FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 01 01 48 00 00 01 2E FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 CB 01 02 01 B3 FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 02 00 2E 00
00 01 14 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 03 01 B4 FF FF FF FF FF FF FF FF 00 FF
A5 10 0F 10 0B 05 03 00 02 00 00 00 E9 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 04 01 B5
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 04 05 16 00 00 01 03 FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 05 01 B6 FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 05 40 C9 00 00 01
F2 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 06 01 B7 FF FF FF FF FF FF FF FF 00 FF A5 10
0F 10 0B 05 06 42 3D 00 00 01 69 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 07 01 B8
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 07 07 4A 00 00 01 3C FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 08 01 B9
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 08 07 3F 00 00 01 32 FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 09 01 BA
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 09 07 37 00 00 01 2B FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 0A 01 BB
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 0A 00 00 00 00 00 EE FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 0B 01 BC
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 0B 0E 4F 00 00 01 4C FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 0C 01 BD
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 0C 00 C8 00 00 01 B8 FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 0D 01 BE
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 0D 00 CA 00 00 01 BB FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 0E 01 BF
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 0E 00 CB 00 00 01 BD
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 0F 01 C0
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 0F 00 CC 00 00 01 BF FF FF FF FF FF FF FF FF 00
FF A5 10 10 20 CB 01 10 01 C1
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 10 0E 35 00 00 01 37
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 11 01 C2
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 0B 05 11 0E 35 00 00 01 38
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 CB 01 12 01 C3 FF FF FF FF FF FF FF FF 00 FF A5 10 0F
10 0B 05 12 0E 35 00 00 01 39
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D1 01 01 01 B8
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 01 06 09 19 0F 37 FF 02 5A FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 D1 01 02 01 B9
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 02 0D 0E 39 0F 08 D5 02 2E FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 D1 01 03 01 BA
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 03 04 0A 0F 0B 00 FF 02 16
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D1 01 04 01 BB
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 04 06 19 00 07 0F 00 01 25 FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 D1 01 05 01 BC
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 05 04 19 00 04 00 00 01 12 FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 D1 01 06 01 BD
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 06 0F 15 0A 17 37 FF 02 6D FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 D1 01 07 01 BE
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 07 0F 00 05 09 14 FF 02 23 FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 D1 01 08 01 BF
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 08 07 19 00 02 00 00 01 16 FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 D1 01 09 01 C0
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 09 02 19 00 03 2D 00 01 40
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D1 01 0A 01 C1
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 0A 09 19 00 04 0F 00 01 2B
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D1 01 0B 01 C2
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 0B 0B 0D 00 0D 0B FF 02 26 FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 D1 01 0C 01 C3
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 11 07 0C 05 0D 14 0D 28 95 01 E8 FF FF FF FF FF FF FF
FF 00 FF A5 10 10 20 E2 01 00 01 C8
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 22 03 07 80 44 01 C4
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 E3 01 00 01 C9
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 23 02 10 00 01 09
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 E8 01 00 01 CE
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 28 0A 00 00 00 FE 01 00 00 00 00 00 02 05 FF FF FF FF
FF FF FF FF 00 FF A5 10 10 20 DE 01 00 01 C4
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 1E 10 00 00 00 00 01 48 00 00 00 2E 00 00 00 02 00 00
01 7B FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 E1 01 00 01 C7 FF FF FF FF FF FF FF FF 00 FF A5
10 0F 10 21 04 01 02 03 04 01 03 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 E0 01 00 01 C6
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 20 0B 00 07 02 01 08 05 06 07 08 09 0A 01 3E
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 DD 01 00 01 C3
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 1D 18 02 00 00 00 80 01 FF FF FF 00 07 02 01 08 05 06
07 08 09 0A 01 02 03 04 04 D2 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D9 01 00 01 BF FF FF FF
FF FF FF FF FF 00 FF A5 10 0F 10 19 16 0B 08 80 1C 85 00 49 6E 74 65 6C 6C 69 63 68 6C 6F 72 2D
2D 34 30 07 DE FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D6 01 00 01 BC FF FF FF FF FF FF FF FF
00 FF A5 10 0F 10 16 10 00 02 00 00 00 01 32 0A 01 90 0D 7A 0F 82 00 00 02 E2
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 E7 01 00 01 CD
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 27 20 08 00 00 00 09 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 2C FF FF FF FF FF FF FF FF 00 FF A5 10 10 20
E0 01 01 01 C7 FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 20 0B 01 01 02 03 04 05 06 07 08 09 0A
01 37 FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D8 01 01 01 BF
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 18 1F 01 80 00 02 00 01 06 02 0C 04 09 0B 07 06 05 80
08 84 03 0F 03 03 D6 80 2E 6C 14 AC E8 20 E8 07 91
FF 00 FF A5 10 10 22 86 02 06 01 01 76
FF FF FF FF FF FF FF FF 00 FF A5 10 22 10 01 01 86 01 6F
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 02 1D 14 1E 20 00 00 00 00 00 00 00 03 00 40 04 39 39
20 00 3A 38 00 00 04 00 00 82 BF 00 0D 03 E2
FF
FF FF FF FF FF FF FF FF 00 FF A5 10 10 20 D8 01 02 01 C0
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 18 1F 02 80 03 02 00 0C 03 05 05 0D 07 0E 0B 00 03 00
03 00 03 00 03 0C E8 DC D0 B8 E8 E8 E8 E8 1C 08 F8
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 02 1D 14 1E 20 00 00 00 00 00 00 00 03 00 40 04 39 39
20 00 3A 38 00 00 04 00 00 82 BF 00 0D 03 E2
FF 00 FF A5 00 60 10 04 01 FF 02 19
FF 00 FF A5 00 10 60 04 01 FF 02 19
FF 00 FF A5 00 60 10 06 01 0A 01 26
FF 00 FF A5 00 10 60 06 01 0A 01 26
FF 00 FF A5 00 60 10 01 04 02 C4 05 14 01 F9
FF 00 FF A5 00 10 60 01 02 05 14 01 31
FF 00 FF A5 00 61 10 04 01 FF 02 1A
FF 00 FF A5 00 10 61 04 01 FF 02 1A
FF 00 FF A5 00 61 10 06 01 04 01 21
FF 00 FF A5 00 10 61 06 01 04 01 21
FF 00 FF A5 10 10 22 D7 01 01 01 C0
FF FF FF FF FF FF FF FF 00 FF A5 10 0F 10 17 10 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80
01 7C
FF 00 FF A5 10 10 22 D7 01 02 01 C1

View File

@ -0,0 +1,122 @@
/**
* Copyright (c) 2010-2024 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.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.openhab.binding.pentair.internal.TestUtilities.parsehex;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.pentair.internal.handler.helpers.PentairControllerSchedule;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
/**
* PentairControllerSchduleTest
*
* @author Jeff James - Initial contribution
*
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class PentairControllerScheduleTest {
//@formatter:off
public static byte[][] packets = {
parsehex("A5 1E 0F 10 11 07 01 06 0A 00 10 00 7F"),
parsehex("A5 1E 0F 10 11 07 02 05 0A 00 0B 00 7F"),
parsehex("A5 1E 0F 10 11 07 03 07 08 00 1A 00 08"),
parsehex("A5 1E 0F 10 11 07 04 09 19 00 02 15 0F")
};
//@formatter:on
@BeforeAll
public static void setUpBeforeClass() throws Exception {
}
@AfterAll
public static void tearDownAfterClass() throws Exception {
}
@BeforeEach
public void setUp() throws Exception {
}
@AfterEach
public void tearDown() throws Exception {
}
@Test
public void parseTest() {
PentairControllerSchedule pcs = new PentairControllerSchedule();
PentairStandardPacket p = new PentairStandardPacket(packets[0]);
pcs.parsePacket(p);
assertThat(pcs.circuit, equalTo(6));
assertThat(pcs.start, equalTo(10 * 60));
assertThat(pcs.end, equalTo(16 * 60));
assertThat(pcs.days, equalTo(0x7F));
assertThat(pcs.type, equalTo(PentairControllerSchedule.ScheduleType.NORMAL));
assertThat(pcs.id, equalTo(1));
PentairStandardPacket p2 = new PentairStandardPacket(packets[1]);
pcs.parsePacket(p2);
assertThat(pcs.circuit, equalTo(5));
assertThat(pcs.start, equalTo(10 * 60));
assertThat(pcs.end, equalTo(11 * 60));
assertThat(pcs.days, equalTo(0x7F));
assertThat(pcs.type, equalTo(PentairControllerSchedule.ScheduleType.NORMAL));
assertThat(pcs.id, equalTo(2));
PentairStandardPacket p3 = new PentairStandardPacket(packets[2]);
pcs.parsePacket(p3);
assertThat(pcs.circuit, equalTo(7));
assertThat(pcs.start, equalTo(8 * 60));
assertThat(pcs.days, equalTo(0x08));
assertThat(pcs.type, equalTo(PentairControllerSchedule.ScheduleType.ONCEONLY));
assertThat(pcs.id, equalTo(3));
PentairStandardPacket p4 = new PentairStandardPacket(packets[3]);
pcs.parsePacket(p4);
assertThat(pcs.circuit, equalTo(9));
assertThat(pcs.end, equalTo(0x02 * 60 + 0x15));
assertThat(pcs.days, equalTo(0x0F));
assertThat(pcs.type, equalTo(PentairControllerSchedule.ScheduleType.EGGTIMER));
assertThat(pcs.id, equalTo(4));
}
@Test
public void setTest() {
PentairControllerSchedule pcs = new PentairControllerSchedule();
pcs.id = 1;
pcs.circuit = 4;
pcs.start = 5 * 60 + 15; // 5:15
pcs.end = 10 * 60 + 30; // 10:30
pcs.type = PentairControllerSchedule.ScheduleType.NORMAL;
pcs.days = 0x07;
PentairStandardPacket p = Objects.requireNonNull(pcs.getWritePacket(0x10, 0x00));
assertThat(p.buf, is(parsehex("A5 00 10 00 91 07 01 04 05 0F 0A 1E 07")));
}
}

View File

@ -0,0 +1,118 @@
/**
* Copyright (c) 2010-2024 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.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.openhab.binding.pentair.internal.TestUtilities.parsehex;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.pentair.internal.handler.helpers.PentairControllerStatus;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairControllerStatusTest}
*
* @author Jeff James - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class PentairControllerStatusTest {
private final Logger logger = LoggerFactory.getLogger(PentairControllerStatusTest.class);
//@formatter:off
public static byte[][] packets = {
parsehex("A5 1E 0F 10 02 1D 09 20 21 00 00 00 00 00 00 20 0F 00 00 04 3F 3F 00 00 41 3C 00 00 07 00 00 6A B6 00 0D"),
parsehex("A5 24 0f 10 02 1d 08 3b 00 01 00 00 00 00 00 20 00 00 00 04 4a 4a 00 00 44 00 00 00 04 00 00 7c e6 00 0d 03 ba"),
parsehex("a5 24 0f 10 02 1d 09 04 00 31 00 00 00 00 00 20 00 00 00 04 4a 4a 00 00 45 00 00 00 04 00 07 ce 60 00 0d 03 85"),
parsehex("A5 1E 0F 10 02 1D 0A 0B 00 00 00 00 00 00 00 21 33 00 00 04 45 45 00 00 3F 3F 00 00 07 00 00 D9 89 00 0D")
};
//@formatter:on
@BeforeAll
public static void setUpBeforeClass() throws Exception {
}
@AfterAll
public static void tearDownAfterClass() throws Exception {
}
@BeforeEach
public void setUp() throws Exception {
}
@AfterEach
public void tearDown() throws Exception {
}
@Test
public void test() {
PentairControllerStatus pcs = new PentairControllerStatus();
PentairStandardPacket p = new PentairStandardPacket(packets[0], packets[0].length);
pcs.parsePacket(p);
logger.debug(pcs.toString());
assertThat(pcs.circuits[0], equalTo(true));
assertThat(pcs.circuits[5], equalTo(true));
assertThat(pcs.pool, equalTo(true));
assertThat(pcs.poolTemp, equalTo(63));
assertThat(pcs.spaTemp, equalTo(63));
assertThat(pcs.airTemp, equalTo(65));
assertThat(pcs.solarTemp, equalTo(60));
p = new PentairStandardPacket(packets[1], packets[1].length);
pcs.parsePacket(p);
logger.debug(pcs.toString());
assertThat(pcs.circuits[8], equalTo(true));
assertThat(pcs.pool, equalTo(false));
assertThat(pcs.poolTemp, equalTo(74));
assertThat(pcs.spaTemp, equalTo(74));
assertThat(pcs.airTemp, equalTo(68));
assertThat(pcs.solarTemp, equalTo(0));
p = new PentairStandardPacket(packets[2], packets[2].length);
pcs.parsePacket(p);
logger.debug(pcs.toString());
assertThat(pcs.circuits[8], equalTo(true));
assertThat(pcs.circuits[12], equalTo(true));
assertThat(pcs.circuits[13], equalTo(true));
assertThat(pcs.pool, equalTo(false));
assertThat(pcs.poolTemp, equalTo(74));
assertThat(pcs.spaTemp, equalTo(74));
assertThat(pcs.airTemp, equalTo(69));
assertThat(pcs.solarTemp, equalTo(0));
p = new PentairStandardPacket(packets[3], packets[3].length);
pcs.parsePacket(p);
logger.debug(pcs.toString());
assertThat(pcs.equip, equalTo(0));
assertThat(pcs.pool, equalTo(false));
assertThat(pcs.poolTemp, equalTo(69));
assertThat(pcs.spaTemp, equalTo(69));
assertThat(pcs.airTemp, equalTo(63));
assertThat(pcs.solarTemp, equalTo(63));
assertThat(pcs.serviceMode, equalTo(true));
}
}

View File

@ -0,0 +1,77 @@
/**
* Copyright (c) 2010-2024 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.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.openhab.binding.pentair.internal.TestUtilities.parsehex;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.pentair.internal.handler.helpers.PentairHeatStatus;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairHeatStatusTest}
*
* @author Jeff James - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
class PentairHeatStatusTest {
private final Logger logger = LoggerFactory.getLogger(PentairHeatStatusTest.class);
//@formatter:off
public static byte[][] packets = {
parsehex("A5 01 0F 10 08 0D 4B 4B 4D 55 5E 07 00 00 58 00 00 00")
};
//@formatter:on
@BeforeAll
static void setUpBeforeClass() throws Exception {
}
@AfterAll
static void tearDownAfterClass() throws Exception {
}
@BeforeEach
void setUp() throws Exception {
}
@AfterEach
void tearDown() throws Exception {
}
@Test
void test() {
PentairHeatStatus hs = new PentairHeatStatus();
PentairStandardPacket p = new PentairStandardPacket(packets[0], packets[0].length);
hs.parsePacket(p);
assertThat(hs.poolSetPoint, equalTo(85));
assertThat(hs.poolHeatMode, equalTo(PentairHeatStatus.HeatMode.SOLAR));
assertThat(hs.spaSetPoint, equalTo(94));
assertThat(hs.spaHeatMode, equalTo(PentairHeatStatus.HeatMode.HEATER));
}
}

View File

@ -0,0 +1,237 @@
/**
* Copyright (c) 2010-2024 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.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.openhab.binding.pentair.internal.TestUtilities.parsehex;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.pentair.internal.handler.helpers.PentairIntelliChem;
import org.openhab.binding.pentair.internal.handler.helpers.PentairIntelliChem.DosingStatus;
import org.openhab.binding.pentair.internal.handler.helpers.PentairIntelliChem.OrpDoserType;
import org.openhab.binding.pentair.internal.handler.helpers.PentairIntelliChem.PhDoserType;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
/**
* PentairIntelliChemTest
*
* @author Jeff James - Initial contribution
*
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class PentairIntelliChemTest {
//@formatter:off
public static byte[][] packets = {
parsehex("A50010901229030202A302D002C60000000000000000000000000006070000C8003F005A3C00580006A5201E010000"),
parsehex("A5100F10122902E302AF02EE02BC000000020000002A0004005C060518019000000096140051000065203C01000000"),
parsehex("A5001090122902E4030202E402BC00000010000000000023000006060300FA002C00A0140051080095005001000000"),
parsehex("A5001090122902F3030502F802EE0000007900000000000000000000FD01C2005000465200550800A2205001000000"),
parsehex("A5001090122902EA02DC02F8028A0000000800000000000000000000F4015E0000004652004D0000A2205001000000")
};
//@formatter:on
@Test
public void test() {
PentairIntelliChem pic = new PentairIntelliChem();
PentairStandardPacket p = new PentairStandardPacket(packets[0], packets[0].length);
pic.parsePacket(p);
assertThat(pic.phReading, equalTo(7.70));
assertThat(pic.orpReading, equalTo(675));
assertThat(pic.phSetPoint, equalTo(7.20));
assertThat(pic.orpSetPoint, equalTo(710));
assertThat(pic.tank1Level, equalTo(0));
assertThat(pic.tank2Level, equalTo(6));
assertThat(pic.calciumHardness, equalTo(0));
assertThat(pic.cyaReading, equalTo(0));
assertThat(pic.alkalinity, equalTo(16128));
// assertThat(pic.alarmWaterFlow, equalTo(fal));
assertThat(pic.lsi, equalTo(0.07));
assertThat(pic.phDoserType, equalTo(PhDoserType.CO2));
assertThat(pic.orpDoserType, equalTo(OrpDoserType.ORP));
assertThat(pic.phDoserStatus, equalTo(DosingStatus.DOSING));
assertThat(pic.orpDoserStatus, equalTo(DosingStatus.DOSING));
assertThat(pic.phDoseTime, equalTo(0));
assertThat(pic.orpDoseTime, equalTo(0));
assertThat(pic.saltLevel, equalTo(4500));
assertThat(pic.calcCalciumHardnessFactor(), equalTo(1.0));
assertThat(pic.alarmWaterFlow, equalTo(false));
assertThat(pic.alarmPh, equalTo(false));
assertThat(pic.alarmOrp, equalTo(true));
assertThat(pic.alarmPhTank, equalTo(false));
assertThat(pic.alarmOrpTank, equalTo(true));
assertThat(pic.alarmProbeFault, equalTo(false));
assertThat(pic.warningPhLockout, equalTo(false));
assertThat(pic.warningPhDailyLimitReached, equalTo(false));
assertThat(pic.warningOrpDailyLimitReached, equalTo(false));
assertThat(pic.warningInvalidSetup, equalTo(false));
assertThat(pic.warningChlorinatorCommError, equalTo(false));
assertThat(pic.firmwareVersion, equalTo("30.032"));
p = new PentairStandardPacket(packets[1], packets[1].length);
pic.parsePacket(p);
assertThat(pic.phReading, equalTo(7.39));
assertThat(pic.orpReading, equalTo(687));
assertThat(pic.phSetPoint, equalTo(7.50));
assertThat(pic.orpSetPoint, equalTo(700));
assertThat(pic.tank1Level, equalTo(6));
assertThat(pic.tank2Level, equalTo(5));
assertThat(pic.calciumHardness, equalTo(400));
assertThat(pic.cyaReading, equalTo(0));
assertThat(pic.alkalinity, equalTo(150));
// assertThat(pic.alarmWaterFlow, equalTo(false));
assertThat(pic.lsi, equalTo(0.24));
assertThat(pic.phDoserType, equalTo(PhDoserType.ACID));
assertThat(pic.orpDoserType, equalTo(OrpDoserType.ORP));
assertThat(pic.phDoserStatus, equalTo(DosingStatus.MONITORING));
assertThat(pic.orpDoserStatus, equalTo(DosingStatus.MIXING));
assertThat(pic.phDoseTime, equalTo(2));
assertThat(pic.orpDoseTime, equalTo(42));
assertThat(pic.saltLevel, equalTo(1000));
assertThat(pic.calcCalciumHardnessFactor(), equalTo(2.2));
assertThat(pic.alarmWaterFlow, equalTo(false));
assertThat(pic.alarmPh, equalTo(false));
assertThat(pic.alarmOrp, equalTo(false));
assertThat(pic.alarmPhTank, equalTo(false));
assertThat(pic.alarmOrpTank, equalTo(false));
assertThat(pic.alarmProbeFault, equalTo(false));
assertThat(pic.warningPhLockout, equalTo(false));
assertThat(pic.warningPhDailyLimitReached, equalTo(false));
assertThat(pic.warningOrpDailyLimitReached, equalTo(false));
assertThat(pic.warningInvalidSetup, equalTo(false));
assertThat(pic.warningChlorinatorCommError, equalTo(false));
assertThat(pic.firmwareVersion, equalTo("1.060"));
p = new PentairStandardPacket(packets[2], packets[2].length);
pic.parsePacket(p);
assertThat(pic.phReading, equalTo(7.4));
assertThat(pic.orpReading, equalTo(770));
assertThat(pic.phSetPoint, equalTo(7.4));
assertThat(pic.orpSetPoint, equalTo(700));
assertThat(pic.tank1Level, equalTo(6));
assertThat(pic.tank2Level, equalTo(6));
assertThat(pic.calciumHardness, equalTo(250));
assertThat(pic.cyaReading, equalTo(44));
assertThat(pic.alkalinity, equalTo(160));
assertThat(pic.lsi, equalTo(0.03));
// assertThat(pic.alarmWaterFlow, equalTo(false));
assertThat(pic.phDoserType, equalTo(PhDoserType.ACID));
assertThat(pic.orpDoserType, equalTo(OrpDoserType.ORP));
assertThat(pic.phDoserStatus, equalTo(DosingStatus.MIXING));
assertThat(pic.orpDoserStatus, equalTo(DosingStatus.MONITORING));
assertThat(pic.phDoseTime, equalTo(16));
assertThat(pic.orpDoseTime, equalTo(0));
assertThat(pic.saltLevel, equalTo(1000));
assertThat(pic.calcCalciumHardnessFactor(), equalTo(2.0));
assertThat(pic.alarmWaterFlow, equalTo(false));
assertThat(pic.alarmPh, equalTo(false));
assertThat(pic.alarmOrp, equalTo(true));
assertThat(pic.alarmPhTank, equalTo(false));
assertThat(pic.alarmOrpTank, equalTo(false));
assertThat(pic.alarmProbeFault, equalTo(false));
assertThat(pic.warningPhLockout, equalTo(false));
assertThat(pic.warningPhDailyLimitReached, equalTo(false));
assertThat(pic.warningOrpDailyLimitReached, equalTo(false));
assertThat(pic.warningInvalidSetup, equalTo(false));
assertThat(pic.warningChlorinatorCommError, equalTo(false));
assertThat(pic.firmwareVersion, equalTo("1.080"));
p = new PentairStandardPacket(packets[3], packets[3].length);
pic.parsePacket(p);
assertThat(pic.phReading, equalTo(7.55));
assertThat(pic.orpReading, equalTo(773));
assertThat(pic.phSetPoint, equalTo(7.6));
assertThat(pic.orpSetPoint, equalTo(750));
assertThat(pic.tank1Level, equalTo(0));
assertThat(pic.tank2Level, equalTo(0));
assertThat(pic.calciumHardness, equalTo(450));
assertThat(pic.cyaReading, equalTo(80));
assertThat(pic.alkalinity, equalTo(70));
assertThat(pic.lsi, equalTo(-0.03));
// assertThat(pic.alarmWaterFlow, equalTo(false));
assertThat(pic.phDoserType, equalTo(PhDoserType.CO2));
assertThat(pic.orpDoserType, equalTo(OrpDoserType.NONE));
assertThat(pic.phDoserStatus, equalTo(DosingStatus.MONITORING));
assertThat(pic.orpDoserStatus, equalTo(DosingStatus.NONE));
assertThat(pic.phDoseTime, equalTo(121));
assertThat(pic.orpDoseTime, equalTo(0));
assertThat(pic.saltLevel, equalTo(4100));
assertThat(pic.calcCalciumHardnessFactor(), equalTo(2.5));
assertThat(pic.alarmWaterFlow, equalTo(false));
assertThat(pic.alarmPh, equalTo(false));
assertThat(pic.alarmOrp, equalTo(true));
assertThat(pic.alarmPhTank, equalTo(false));
assertThat(pic.alarmOrpTank, equalTo(false));
assertThat(pic.alarmProbeFault, equalTo(false));
assertThat(pic.warningPhLockout, equalTo(false));
assertThat(pic.warningPhDailyLimitReached, equalTo(false));
assertThat(pic.warningOrpDailyLimitReached, equalTo(false));
assertThat(pic.warningInvalidSetup, equalTo(false));
assertThat(pic.warningChlorinatorCommError, equalTo(false));
assertThat(pic.firmwareVersion, equalTo("1.080"));
p = new PentairStandardPacket(packets[4], packets[4].length);
pic.parsePacket(p);
assertThat(pic.phReading, equalTo(7.46));
assertThat(pic.orpReading, equalTo(732));
assertThat(pic.phSetPoint, equalTo(7.6));
assertThat(pic.orpSetPoint, equalTo(650));
assertThat(pic.tank1Level, equalTo(0));
assertThat(pic.tank2Level, equalTo(0));
assertThat(pic.calciumHardness, equalTo(350));
assertThat(pic.cyaReading, equalTo(0));
assertThat(pic.alkalinity, equalTo(70));
assertThat(pic.lsi, equalTo(-0.12));
// assertThat(pic.alarmWaterFlow, equalTo(false));
assertThat(pic.phDoserType, equalTo(PhDoserType.CO2));
assertThat(pic.orpDoserType, equalTo(OrpDoserType.NONE));
assertThat(pic.phDoserStatus, equalTo(DosingStatus.MONITORING));
assertThat(pic.orpDoserStatus, equalTo(DosingStatus.NONE));
assertThat(pic.phDoseTime, equalTo(8));
assertThat(pic.orpDoseTime, equalTo(0));
assertThat(pic.saltLevel, equalTo(4100));
assertThat(pic.calcCalciumHardnessFactor(), equalTo(2.2));
assertThat(pic.alarmWaterFlow, equalTo(false));
assertThat(pic.alarmPh, equalTo(false));
assertThat(pic.alarmOrp, equalTo(false));
assertThat(pic.alarmPhTank, equalTo(false));
assertThat(pic.alarmOrpTank, equalTo(false));
assertThat(pic.alarmProbeFault, equalTo(false));
assertThat(pic.warningPhLockout, equalTo(false));
assertThat(pic.warningPhDailyLimitReached, equalTo(false));
assertThat(pic.warningOrpDailyLimitReached, equalTo(false));
assertThat(pic.warningInvalidSetup, equalTo(false));
assertThat(pic.warningChlorinatorCommError, equalTo(false));
assertThat(pic.firmwareVersion, equalTo("1.080"));
}
}

View File

@ -0,0 +1,202 @@
/**
* Copyright (c) 2010-2024 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.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Mockito.*;
import static org.openhab.binding.pentair.internal.TestUtilities.parsehex;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.pentair.internal.parser.PentairIntelliChlorPacket;
import org.openhab.binding.pentair.internal.parser.PentairParser;
import org.openhab.binding.pentair.internal.parser.PentairParser.CallbackPentairParser;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* PentairParserTest
*
* @author Jeff James - Initial contribution
*
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
class PentairParserTest {
private final Logger logger = LoggerFactory.getLogger(PentairParserTest.class);
//@formatter:off
public static byte[] stream = parsehex(
"FF 00 FF A5 1E 0F 10 02 1D 09 1F 00 00 00 00 00 00 00 20 03 00 00 04 3F 3F 00 00 41 3C 00 00 07 00 00 6A B6 00 0D 03 7F"
+ "FF 00 FF A5 10 0F 10 12 29 02 E3 02 AF 02 EE 02 BC 00 00 00 02 00 00 00 2A 00 04 00 5C 06 05 18 01 90 00 00 00 96 14 00 51 00 00 65 20 3C 01 00 00 00 07 50 "
+ "FF 00 FF A5 01 0F 10 02 1D 0D 1D 20 00 00 00 00 00 00 00 33 00 00 04 4D 4D 00 00 51 6D 00 00 07 00 00 5E D5 00 0D 04 04");
//@formatter:on
PentairParser parser = new PentairParser();
@Mock
@NonNullByDefault({})
CallbackPentairParser callback;
@Captor
@NonNullByDefault({})
ArgumentCaptor<PentairStandardPacket> packetsStandard;
@Captor
@NonNullByDefault({})
ArgumentCaptor<PentairIntelliChlorPacket> packetsIntellichlor;
@NonNullByDefault({})
Thread thread;
@BeforeEach
public void setUp() throws Exception {
parser.setCallback(callback);
}
@AfterEach
public void tearDown() throws Exception {
if (thread != null) {
thread.interrupt();
thread.join();
}
thread = null;
}
@Test
public void test() throws InterruptedException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(stream, 0, stream.length);
parser.setInputStream(inputStream);
thread = new Thread(parser);
thread.start();
Thread.sleep(2000);
thread.interrupt();
thread.join();
thread = null;
verify(callback, times(3)).onPentairPacket(packetsStandard.capture());
List<PentairStandardPacket> allPackets = new ArrayList<PentairStandardPacket>();
allPackets = packetsStandard.getAllValues();
assertThat(allPackets.size(), equalTo(3));
logger.info("1: {}", allPackets.get(0).getByte(PentairStandardPacket.ACTION));
logger.info("2: {}", allPackets.get(1).getByte(PentairStandardPacket.ACTION));
logger.info("3: {}", allPackets.get(2).getByte(PentairStandardPacket.ACTION));
assertThat(allPackets.get(0).getByte(PentairStandardPacket.ACTION), equalTo(0x02));
assertThat(allPackets.get(1).getByte(PentairStandardPacket.ACTION), equalTo(0x12));
assertThat(allPackets.get(2).getByte(PentairStandardPacket.ACTION), equalTo(0x02));
}
@Test
public void testNodeJSCapture() throws InterruptedException, IOException {
byte[] array = parsehex(Files.readAllBytes(Paths.get("src/test/data/nodejs-capture.dat")));
logger.info("testNodeJSCapture");
ByteArrayInputStream inputStream = new ByteArrayInputStream(array, 0, array.length);
parser.setInputStream(inputStream);
thread = new Thread(parser);
thread.start();
Thread.sleep(2000);
thread.interrupt();
thread.join();
thread = null;
verify(callback, atLeast(1)).onPentairPacket(packetsStandard.capture());
verify(callback, atLeast(1)).onIntelliChlorPacket(packetsIntellichlor.capture());
List<PentairStandardPacket> allPackets = new ArrayList<PentairStandardPacket>();
allPackets = packetsStandard.getAllValues();
logger.info("Number of Pentair packets: {}", allPackets.size());
assertThat(allPackets.size(), equalTo(281));
List<PentairIntelliChlorPacket> allPacketsIntellichlor = new ArrayList<PentairIntelliChlorPacket>();
allPacketsIntellichlor = packetsIntellichlor.getAllValues();
logger.info("Number of Intellichlor packets: {}", allPacketsIntellichlor.size());
assertThat(allPacketsIntellichlor.size(), equalTo(1));
}
@Test
public void parseEasyTouch8() throws IOException, InterruptedException {
byte[] array = parsehex(Files.readAllBytes(Paths.get("src/test/data/easytouch8.dat")));
logger.info("parseEasyTouch8");
// logger.debug("{}", javax.xml.bind.DatatypeConverter.printHexBinary(array));
ByteArrayInputStream inputStream = new ByteArrayInputStream(array, 0, array.length);
parser.setInputStream(inputStream);
thread = new Thread(parser);
thread.start();
Thread.sleep(2000);
thread.interrupt();
thread.join();
thread = null;
verify(callback, atLeast(1)).onPentairPacket(packetsStandard.capture());
verify(callback, atLeast(1)).onIntelliChlorPacket(packetsIntellichlor.capture());
List<PentairStandardPacket> allPackets = new ArrayList<PentairStandardPacket>();
allPackets = packetsStandard.getAllValues();
logger.info("Number of Pentair packets: {}", allPackets.size());
assertThat(allPackets.size(), equalTo(1032));
List<PentairIntelliChlorPacket> allPacketsIntellichlor = new ArrayList<PentairIntelliChlorPacket>();
allPacketsIntellichlor = packetsIntellichlor.getAllValues();
logger.info("Number of Intellichlor packets: {}", allPacketsIntellichlor.size());
assertThat(allPacketsIntellichlor.size(), equalTo(36));
}
}

View File

@ -0,0 +1,100 @@
/**
* Copyright (c) 2010-2024 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.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.openhab.binding.pentair.internal.TestUtilities.parsehex;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.pentair.internal.handler.helpers.PentairPumpStatus;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairPumpStatusTest}
*
* @author Jeff James - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class PentairPumpStatusTest {
private final Logger logger = LoggerFactory.getLogger(PentairPumpStatus.class);
//@formatter:off
public static byte[][] packets = {
parsehex("A5 00 22 60 07 0F 0A 02 02 00 E7 06 D6 00 00 00 00 00 01 02 03"),
parsehex("A5 00 22 60 07 0F 0A 00 00 01 F9 07 D5 00 00 00 00 09 21 0A 3A"), // SVRS alarm
parsehex("a5 00 10 60 07 0f 0a 02 02 00 5a 02 ee 00 00 00 00 00 01 15 1f"),
parsehex("A5 00 10 60 07 0F 04 00 00 00 00 00 00 00 00 00 00 00 00 14 1E")
};
//@formatter:on
@BeforeAll
public static void setUpBeforeClass() throws Exception {
}
@AfterAll
public static void tearDownAfterClass() throws Exception {
}
@BeforeEach
public void setUp() throws Exception {
}
@AfterEach
public void tearDown() throws Exception {
}
@Test
public void test() {
PentairPumpStatus ps = new PentairPumpStatus();
PentairStandardPacket p = new PentairStandardPacket(packets[0], packets[0].length);
ps.parsePacket(p);
logger.debug(ps.toString());
assertThat(ps.run, equalTo(true));
assertThat(ps.mode, equalTo(2));
assertThat(ps.power, equalTo(231));
assertThat(ps.rpm, equalTo(1750));
p = new PentairStandardPacket(packets[1], packets[1].length);
ps.parsePacket(p);
logger.debug(ps.toString());
assertThat(ps.run, equalTo(true));
assertThat(ps.mode, equalTo(0));
assertThat(ps.power, equalTo(505));
assertThat(ps.rpm, equalTo(2005));
p = new PentairStandardPacket(packets[2], packets[2].length);
ps.parsePacket(p);
logger.debug(ps.toString());
p = new PentairStandardPacket(packets[3], packets[3].length);
ps.parsePacket(p);
logger.debug(ps.toString());
assertThat(ps.run, equalTo(false));
assertThat(ps.mode, equalTo(0));
assertThat(ps.power, equalTo(0));
assertThat(ps.rpm, equalTo(0));
}
}

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2010-2024 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 org.eclipse.jdt.annotation.NonNullByDefault;
/**
* TestUtilities
*
* @author Jeff James - Initial contribution
*
*/
@NonNullByDefault
public class TestUtilities {
public static byte[] parsehex(String in) {
String out = in.replaceAll("\\s", "");
return javax.xml.bind.DatatypeConverter.parseHexBinary(out);
}
private static int hexToBin(byte in) {
if ('0' <= in && in <= '9') {
return in - '0';
}
if ('A' <= in && in <= 'F') {
return in - 'A' + 10;
}
if ('a' <= in && in <= 'f') {
return in - 'a' + 10;
}
return -1;
}
public static byte[] parsehex(byte[] in) {
byte[] out = new byte[in.length / 2 + 1];
int i = 0;
int length = 0;
while (i < in.length) {
int h = hexToBin(in[i]);
i++;
if (h == -1) {
continue;
}
if (i >= in.length) {
break;
}
int l = hexToBin(in[i]);
i++;
if (l == -1) {
continue;
}
out[length++] = (byte) (h * 16 + l);
}
return out;
}
}

View File

@ -0,0 +1,211 @@
/**
* Copyright (c) 2010-2024 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.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
import static org.openhab.binding.pentair.internal.TestUtilities.parsehex;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelGroupUID;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairControllerHandlerTest }
*
* @author Jeff James - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class PentairControllerHandlerTest {
@SuppressWarnings("unused")
private final Logger logger = LoggerFactory.getLogger(PentairControllerHandlerTest.class);
//@formatter:off
public static byte[][] packets = {
parsehex("A5 1E 0F 10 02 1D 09 20 21 00 00 00 00 00 00 20 0F 00 00 04 3F 3F 00 00 41 3C 00 00 07 00 00 6A B6 00 0D"),
parsehex("A5 24 0f 10 02 1d 08 3b 00 01 00 00 00 00 00 20 00 00 00 04 4a 4a 00 00 44 00 00 00 04 00 00 7c e6 00 0d"),
parsehex("a5 24 0f 10 02 1d 09 04 00 31 00 00 00 00 00 20 00 00 00 04 4a 4a 00 00 45 00 00 00 04 00 07 ce 60 00 0d"),
parsehex("A5 1E 0F 10 02 1D 0A 0B 00 00 00 00 00 00 00 21 33 00 00 04 45 45 00 00 3F 3F 00 00 07 00 00 D9 89 00 0D")
};
//@formatter:on
public class NullOutputStream extends OutputStream {
@Override
public void write(int b) throws IOException {
}
}
@Mock
private @NonNullByDefault({}) Bridge bridgeMock;
@Mock
private @NonNullByDefault({}) ThingHandlerCallback callback;
@Mock
private @NonNullByDefault({}) Thing thing;
@Mock
private @NonNullByDefault({}) PentairIPBridgeHandler pibh;
private @NonNullByDefault({}) PentairControllerHandler handler;
private String uid = "1:2:3";
private ThingUID thingUID = new ThingUID(uid);
private List<Channel> statusChannels = new ArrayList<Channel>();
private List<Channel> spaCircuitChannels = new ArrayList<Channel>();
private List<Channel> poolCircuitChannels = new ArrayList<Channel>();
@BeforeAll
public static void setUpBeforeClass() throws Exception {
}
@AfterAll
public static void tearDownAfterClass() throws Exception {
}
@BeforeEach
public void setUp() throws Exception {
pibh = new PentairIPBridgeHandler(bridgeMock);
OutputStream outputStream = new NullOutputStream();
pibh.setOutputStream(outputStream);
handler = new PentairControllerHandler(thing) {
@Override
public @NonNull PentairBaseBridgeHandler getBridgeHandler() {
return pibh;
}
};
ChannelGroupUID groupID = new ChannelGroupUID(thingUID, GROUP_CONTROLLER_STATUS);
statusChannels.add(ChannelBuilder.create(new ChannelUID(groupID, CHANNEL_CONTROLLER_AIRTEMPERATURE)).build());
statusChannels.add(ChannelBuilder.create(new ChannelUID(groupID, CHANNEL_CONTROLLER_SOLARTEMPERATURE)).build());
statusChannels.add(ChannelBuilder.create(new ChannelUID(groupID, CHANNEL_CONTROLLER_LIGHTMODE)).build());
statusChannels.add(ChannelBuilder.create(new ChannelUID(groupID, CHANNEL_CONTROLLER_SERVICEMODE)).build());
statusChannels.add(ChannelBuilder.create(new ChannelUID(groupID, CHANNEL_CONTROLLER_SOLARON)).build());
statusChannels.add(ChannelBuilder.create(new ChannelUID(groupID, CHANNEL_CONTROLLER_HEATERON)).build());
statusChannels.add(ChannelBuilder.create(new ChannelUID(groupID, CHANNEL_CONTROLLER_HEATERDELAY)).build());
groupID = new ChannelGroupUID(thingUID, GROUP_CONTROLLER_SPACIRCUIT);
spaCircuitChannels
.add(ChannelBuilder.create(new ChannelUID(groupID, CHANNEL_CONTROLLER_CIRCUITSWITCH)).build());
spaCircuitChannels.add(ChannelBuilder.create(new ChannelUID(groupID, CHANNEL_CONTROLLER_CIRCUITNAME)).build());
spaCircuitChannels
.add(ChannelBuilder.create(new ChannelUID(groupID, CHANNEL_CONTROLLER_CIRCUITFUNCTION)).build());
groupID = new ChannelGroupUID(thingUID, GROUP_CONTROLLER_POOLCIRCUIT);
poolCircuitChannels
.add(ChannelBuilder.create(new ChannelUID(groupID, CHANNEL_CONTROLLER_CIRCUITSWITCH)).build());
poolCircuitChannels.add(ChannelBuilder.create(new ChannelUID(groupID, CHANNEL_CONTROLLER_CIRCUITNAME)).build());
poolCircuitChannels
.add(ChannelBuilder.create(new ChannelUID(groupID, CHANNEL_CONTROLLER_CIRCUITFUNCTION)).build());
// channels.add(ChannelBuilder.create(new ChannelUID(groupID, )).build());
when(thing.getConfiguration()).thenReturn(new Configuration(Collections.singletonMap("id", 0x10)));
when(thing.getHandler()).thenReturn(handler);
when(thing.getUID()).thenReturn(thingUID);
when(thing.getChannelsOfGroup(eq(GROUP_CONTROLLER_STATUS))).thenReturn(statusChannels);
when(thing.getChannelsOfGroup(eq(GROUP_CONTROLLER_SPACIRCUIT))).thenReturn(spaCircuitChannels);
when(thing.getChannelsOfGroup(eq(GROUP_CONTROLLER_POOLCIRCUIT))).thenReturn(poolCircuitChannels);
handler.setCallback(callback);
}
public List<Channel> getChannelsOfGroup(String uid) {
return statusChannels;
}
@AfterEach
public void tearDown() throws Exception {
handler.dispose();
}
@Test
@MockitoSettings(strictness = Strictness.LENIENT)
public void testPacketProcessing() {
handler.initialize();
verify(callback, times(1)).statusUpdated(eq(thing),
argThat(arg -> arg.getStatus().equals(ThingStatus.UNKNOWN)));
PentairStandardPacket p = new PentairStandardPacket(packets[0], packets[0].length);
handler.processPacketFrom(p);
verify(callback, times(1)).statusUpdated(eq(thing), argThat(arg -> arg.getStatus().equals(ThingStatus.ONLINE)));
ChannelUID cuid = new ChannelUID(thingUID, GROUP_CONTROLLER_SPACIRCUIT + "#switch");
verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON);
cuid = new ChannelUID(thingUID, GROUP_CONTROLLER_POOLCIRCUIT + "#switch");
verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON);
cuid = new ChannelUID(thingUID, GROUP_CONTROLLER_STATUS + "#" + CHANNEL_CONTROLLER_AIRTEMPERATURE);
verify(callback, times(1)).stateUpdated(cuid, new QuantityType<>(65, ImperialUnits.FAHRENHEIT));
Mockito.reset(callback);
p = new PentairStandardPacket(packets[1], packets[1].length);
handler.processPacketFrom(p);
Mockito.reset(callback);
p = new PentairStandardPacket(packets[2], packets[2].length);
handler.processPacketFrom(p);
cuid = new ChannelUID(thingUID, GROUP_CONTROLLER_POOLCIRCUIT + "#switch");
verify(callback, times(1)).stateUpdated(cuid, OnOffType.OFF);
cuid = new ChannelUID(thingUID, GROUP_CONTROLLER_FEATURE3 + "#switch");
// verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON);
cuid = new ChannelUID(thingUID, GROUP_CONTROLLER_FEATURE4 + "#switch");
// verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON);
cuid = new ChannelUID(thingUID, GROUP_CONTROLLER_STATUS + "#" + CHANNEL_CONTROLLER_AIRTEMPERATURE);
verify(callback, times(1)).stateUpdated(cuid, new QuantityType<>(69, ImperialUnits.FAHRENHEIT));
p = new PentairStandardPacket(packets[3], packets[3].length);
handler.processPacketFrom(p);
}
}

View File

@ -0,0 +1,163 @@
/**
* Copyright (c) 2010-2024 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.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
import static org.openhab.binding.pentair.internal.TestUtilities.parsehex;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
/**
* PentairIntelliChemHandlerTest
*
* @author Jeff James - Initial contribution
*
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class PentairIntelliChemHandlerTest {
//@formatter:off
public static byte[][] packets = {
parsehex("A50010901229030202A302D002C60000000000000000000000000006070000C8003F005A3C00580006A5201E01000000"),
parsehex("A5100F10122902E302AF02EE02BC000000020000002A0004005C060518019000000096140051000065203C0100000000")
};
//@formatter:on
private @NonNullByDefault({}) PentairIntelliChemHandler pich;
private String uid = "1:2:3";
private ThingUID thingUID = new ThingUID(uid);
private List<Channel> channels = new ArrayList<Channel>();
@Mock
private @NonNullByDefault({}) Bridge bridge;
@Mock
private @NonNullByDefault({}) ThingHandlerCallback callback;
@Mock
private @NonNullByDefault({}) Thing thing;
@Mock
private @NonNullByDefault({}) PentairIPBridgeHandler pibh;
@BeforeAll
public static void setUpBeforeClass() throws Exception {
}
@AfterAll
public static void tearDownAfterClass() throws Exception {
}
@BeforeEach
public void setUp() throws Exception {
pibh = new PentairIPBridgeHandler(bridge);
pich = new PentairIntelliChemHandler(thing) {
@Override
public @NonNull PentairBaseBridgeHandler getBridgeHandler() {
return pibh;
}
};
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_PHREADING)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_ORPREADING)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_PHSETPOINT)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_ORPSETPOINT)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_TANK1LEVEL)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_TANK2LEVEL)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_CALCIUMHARDNESS)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_CYAREADING)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_ALKALINITY)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_PHDOSERTYPE)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_ORPDOSERTYPE)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_PHDOSERSTATUS)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_ORPDOSERSTATUS)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_PHDOSETIME)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_ORPDOSETIME)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_LSI)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_SALTLEVEL)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_ALARMWATERFLOW)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_ALARMPH)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_ALARMORP)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_ALARMPHTANK)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_ALARMORPTANK)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_ALARMPROBEFAULT)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_WARNINGPHLOCKOUT)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_WARNINGPHDAILYLIMITREACHED))
.build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_WARNINGORPDAILYLIMITREACHED))
.build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_WARNINGINVALIDSETUP)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHEM_WARNINGCHLORINATORCOMMERROR))
.build());
when(thing.getConfiguration()).thenReturn(new Configuration(Collections.singletonMap("id", 144)));
when(thing.getHandler()).thenReturn(pich);
when(thing.getChannels()).thenReturn(channels);
pich.setCallback(callback);
}
@AfterEach
public void tearDown() throws Exception {
pich.dispose();
}
@Test
public void test() {
pich.initialize();
PentairStandardPacket p = new PentairStandardPacket(packets[0], packets[0].length);
verify(callback).statusUpdated(eq(thing), argThat(arg -> arg.getStatus().equals(ThingStatus.UNKNOWN)));
pich.processPacketFrom(p);
verify(callback).statusUpdated(eq(thing), argThat(arg -> arg.getStatus().equals(ThingStatus.ONLINE)));
ChannelUID cuid = new ChannelUID(thingUID, CHANNEL_INTELLICHEM_PHREADING);
verify(callback, times(1)).stateUpdated(cuid, new DecimalType(7.7));
cuid = new ChannelUID(thingUID, CHANNEL_INTELLICHEM_ORPREADING);
verify(callback, times(1)).stateUpdated(cuid, new DecimalType(675));
}
}

View File

@ -0,0 +1,187 @@
/**
* Copyright (c) 2010-2024 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.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
import static org.openhab.binding.pentair.internal.TestUtilities.parsehex;
import java.util.ArrayList;
import java.util.List;
import javax.measure.quantity.Dimensionless;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.pentair.internal.parser.PentairIntelliChlorPacket;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
/**
* PentairIntelliChloreHandlerTest
*
* @author Jeff James - Initial contribution
*
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class PentairIntelliChlorHandlerTest {
//@formatter:off
public static byte[][] packets = {
parsehex("10 02 50 11 50"),
parsehex("10 02 00 12 67 80"),
parsehex("10 02 50 14 00"),
parsehex("10 02 50 11 00"),
parsehex("10 02 00 12 4C 81"),
parsehex("10 02 00 03 00 49 6E 74 65 6C 6C 69 63 68 6C 6F 72 2D 2D 34 30"),
parsehex("10 02 00 12 4C 81")
};
//@formatter:on
private @NonNullByDefault({}) PentairIntelliChlorHandler handler;
private String uid = "1:2:3";
private ThingUID thingUID = new ThingUID(uid);
@Mock
private @NonNullByDefault({}) Bridge bridge;
@Mock
private @NonNullByDefault({}) ThingHandlerCallback callback;
@Mock
private @NonNullByDefault({}) Thing thing;
@Mock
private @NonNullByDefault({}) PentairIPBridgeHandler pibh;
@BeforeAll
public static void setUpBeforeClass() throws Exception {
}
@AfterAll
public static void tearDownAfterClass() throws Exception {
}
@BeforeEach
public void setUp() throws Exception {
pibh = new PentairIPBridgeHandler(bridge);
handler = new PentairIntelliChlorHandler(thing) {
@Override
public @NonNull PentairBaseBridgeHandler getBridgeHandler() {
return pibh;
}
};
List<Channel> channels = new ArrayList<Channel>();
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHLOR_PROPERTYVERSION)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHLOR_PROPERTYMODEL)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHLOR_SALTOUTPUT)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHLOR_SALINITY)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHLOR_OK)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHLOR_LOWFLOW)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHLOR_LOWSALT)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHLOR_VERYLOWSALT)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHLOR_HIGHCURRENT)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHLOR_CLEANCELL)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHLOR_LOWVOLTAGE)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHLOR_LOWWATERTEMP)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLICHLOR_COMMERROR)).build());
when(thing.getConfiguration()).thenReturn(new Configuration());
when(thing.getUID()).thenReturn(thingUID);
when(thing.getHandler()).thenReturn(handler);
when(thing.getChannels()).thenReturn(channels);
handler.setCallback(callback);
}
@AfterEach
public void tearDown() throws Exception {
handler.dispose();
}
@Test
public void test() {
handler.initialize();
verify(callback).statusUpdated(eq(thing), argThat(arg -> arg.getStatus().equals(ThingStatus.UNKNOWN)));
assertThat(handler.getPentairID(), equalTo(0));
PentairIntelliChlorPacket p = new PentairIntelliChlorPacket(packets[0], packets[0].length);
handler.processPacketFrom(p);
ChannelUID cuid = new ChannelUID(new ThingUID("1:2:3"), CHANNEL_INTELLICHLOR_SALTOUTPUT);
verify(callback, times(1)).stateUpdated(cuid, new QuantityType<Dimensionless>(80, Units.PERCENT));
p = new PentairIntelliChlorPacket(packets[1], packets[1].length);
handler.processPacketFrom(p);
// Won't actually go ONLINE until a packet FROM the intellichlor
verify(callback, times(1)).statusUpdated(eq(thing), argThat(arg -> arg.getStatus().equals(ThingStatus.ONLINE)));
cuid = new ChannelUID(new ThingUID("1:2:3"), CHANNEL_INTELLICHLOR_SALINITY);
verify(callback, times(1)).stateUpdated(cuid, new QuantityType<Dimensionless>(5150, Units.PARTS_PER_MILLION));
cuid = new ChannelUID(new ThingUID("1:2:3"), CHANNEL_INTELLICHLOR_OK);
verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON);
p = new PentairIntelliChlorPacket(packets[2], packets[2].length);
handler.processPacketFrom(p);
p = new PentairIntelliChlorPacket(packets[3], packets[3].length);
handler.processPacketFrom(p);
cuid = new ChannelUID(new ThingUID("1:2:3"), CHANNEL_INTELLICHLOR_SALTOUTPUT);
verify(callback, times(1)).stateUpdated(cuid, new QuantityType<Dimensionless>(0, Units.PERCENT));
p = new PentairIntelliChlorPacket(packets[4], packets[4].length);
handler.processPacketFrom(p);
cuid = new ChannelUID(new ThingUID("1:2:3"), CHANNEL_INTELLICHLOR_SALINITY);
verify(callback, times(1)).stateUpdated(cuid, new QuantityType<Dimensionless>(3800, Units.PARTS_PER_MILLION));
cuid = new ChannelUID(new ThingUID("1:2:3"), CHANNEL_INTELLICHLOR_OK);
verify(callback, times(1)).stateUpdated(cuid, OnOffType.OFF);
cuid = new ChannelUID(new ThingUID("1:2:3"), CHANNEL_INTELLICHLOR_LOWFLOW);
verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON);
p = new PentairIntelliChlorPacket(packets[5], packets[5].length);
handler.processPacketFrom(p);
assertThat(handler.version, equalTo(0));
assertThat(handler.name, equalTo("Intellichlor--40"));
p = new PentairIntelliChlorPacket(packets[6], packets[6].length);
handler.processPacketFrom(p);
assertThat(handler.version, equalTo(0));
assertThat(handler.name, equalTo("Intellichlor--40"));
}
}

View File

@ -0,0 +1,177 @@
/**
* Copyright (c) 2010-2024 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.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
import static org.openhab.binding.pentair.internal.TestUtilities.parsehex;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.pentair.internal.parser.PentairStandardPacket;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentailIntelliFlowHandlerTest }
*
* @author Jeff James - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class PentairIntelliFloHandlerTest {
@SuppressWarnings("unused")
private final Logger logger = LoggerFactory.getLogger(PentairIntelliFloHandlerTest.class);
//@formatter:off
public static byte[][] packets = {
parsehex("A5 00 22 60 07 0F 0A 02 02 00 E7 06 D6 00 00 00 00 00 01 02 03"),
parsehex("A5 00 22 60 07 0F 0A 00 00 01 F9 07 D5 00 00 00 00 09 21 0A 3A"), // SVRS alarm
parsehex("a5 00 10 60 07 0f 0a 02 02 00 5a 02 ee 00 00 00 00 00 01 15 1f"),
parsehex("A5 00 10 60 07 0F 04 00 00 00 00 00 00 00 00 00 00 00 00 14 1E")
};
//@formatter:on
private @NonNullByDefault({}) PentairIntelliFloHandler handler;
private String uid = "1:2:3";
private ThingUID thingUID = new ThingUID(uid);
private List<Channel> channels = new ArrayList<Channel>();
@Mock
private @NonNullByDefault({}) Bridge bridge;
@Mock
private @NonNullByDefault({}) ThingHandlerCallback callback;
@Mock
private @NonNullByDefault({}) Thing thing;
@Mock
private @NonNullByDefault({}) PentairIPBridgeHandler pibh;
@BeforeAll
public static void setUpBeforeClass() throws Exception {
}
@AfterAll
public static void tearDownAfterClass() throws Exception {
}
@BeforeEach
public void setUp() throws Exception {
pibh = new PentairIPBridgeHandler(bridge);
handler = new PentairIntelliFloHandler(thing) {
@Override
public @NonNull PentairBaseBridgeHandler getBridgeHandler() {
return pibh;
}
};
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLIFLO_RUN)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLIFLO_POWER)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_INTELLIFLO_RPM)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, INTELLIFLO_GPM)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, INTELLIFLO_ERROR)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, INTELLIFLO_STATUS1)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, INTELLIFLO_STATUS2)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, INTELLIFLO_TIMER)).build());
channels.add(ChannelBuilder.create(new ChannelUID(thingUID, INTELLIFLO_RUNPROGRAM)).build());
when(thing.getConfiguration()).thenReturn(new Configuration(Collections.singletonMap("id", 0x10)));
when(thing.getHandler()).thenReturn(handler);
when(thing.getChannels()).thenReturn(channels);
handler.setCallback(callback);
}
@AfterEach
public void tearDown() throws Exception {
handler.dispose();
}
@Test
public void testPacketProcessing() {
ChannelUID cuid;
PentairStandardPacket p;
handler.initialize();
verify(callback, times(1)).statusUpdated(eq(thing),
argThat(arg -> arg.getStatus().equals(ThingStatus.UNKNOWN)));
p = new PentairStandardPacket(packets[0], packets[0].length);
handler.processPacketFrom(p);
verify(callback, times(1)).statusUpdated(eq(thing), argThat(arg -> arg.getStatus().equals(ThingStatus.ONLINE)));
cuid = new ChannelUID(thingUID, CHANNEL_INTELLIFLO_RUN);
verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON);
cuid = new ChannelUID(thingUID, CHANNEL_INTELLIFLO_POWER);
verify(callback, times(1)).stateUpdated(cuid, new QuantityType<>(231, Units.WATT));
cuid = new ChannelUID(thingUID, CHANNEL_INTELLIFLO_RPM);
verify(callback, times(1)).stateUpdated(cuid, new DecimalType(1750));
Mockito.reset(callback);
p = new PentairStandardPacket(packets[1], packets[1].length);
handler.processPacketFrom(p);
cuid = new ChannelUID(thingUID, CHANNEL_INTELLIFLO_RUN);
verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON);
cuid = new ChannelUID(thingUID, CHANNEL_INTELLIFLO_POWER);
verify(callback, times(1)).stateUpdated(cuid, new QuantityType<>(505, Units.WATT));
cuid = new ChannelUID(thingUID, CHANNEL_INTELLIFLO_RPM);
verify(callback, times(1)).stateUpdated(cuid, new DecimalType(2005));
Mockito.reset(callback);
p = new PentairStandardPacket(packets[2], packets[2].length);
handler.processPacketFrom(p);
Mockito.reset(callback);
p = new PentairStandardPacket(packets[3], packets[3].length);
handler.processPacketFrom(p);
cuid = new ChannelUID(thingUID, CHANNEL_INTELLIFLO_RUN);
verify(callback, times(1)).stateUpdated(cuid, OnOffType.OFF);
cuid = new ChannelUID(thingUID, CHANNEL_INTELLIFLO_POWER);
verify(callback, times(1)).stateUpdated(cuid, new QuantityType<>(0, Units.WATT));
cuid = new ChannelUID(thingUID, CHANNEL_INTELLIFLO_RPM);
verify(callback, times(1)).stateUpdated(cuid, new DecimalType(0));
}
}