mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[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:
parent
e1d0204751
commit
663c111e1e
@ -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 Salinity "Salinity [%d ppm]" (gPool) { channel = "pentair:intellichlor:1:ic40:salinity" }
|
||||
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 Pump_Run "Pump running [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:run" }
|
||||
Number Pump_DriveState "Pump drivestate [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:drivestate" }
|
||||
Number Pump_Mode "Pump Mode [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:mode" }
|
||||
Number Pump_RPM "Pump RPM [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:rpm" }
|
||||
Number Pump_Power "Pump Power [%d W]" (gPool) { channel = "pentair:intelliflo:1:pump1:power" }
|
||||
Number Pump_Error "Pump Error [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:error" }
|
||||
Number Pump_PPC "Pump PPC [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:ppc" }
|
||||
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 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_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.
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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 = "";
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
||||
}
|
||||
public @Nullable PentairControllerHandler findController() {
|
||||
List<Thing> things = getThing().getThings();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
for (Thing t : things) {
|
||||
PentairBaseThingHandler handler = (PentairBaseThingHandler) t.getHandler();
|
||||
|
||||
c = reader.read();
|
||||
if (c == -1) {
|
||||
// EOBException is thrown if no more bytes in buffer. This exception is used to exit the parser when full
|
||||
// packet is not in buffer
|
||||
throw new EOBException();
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific number of bytes from reader input stream
|
||||
*
|
||||
* @param buf byte buffer to store bytes
|
||||
* @param start starting index to store bytes
|
||||
* @param n number of bytes to read
|
||||
* @return number of bytes read
|
||||
* @throws EOBException
|
||||
* @throws IOException
|
||||
*/
|
||||
private int getBytes(byte[] buf, int start, int n) throws EOBException, IOException {
|
||||
int i;
|
||||
int c;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
c = reader.read();
|
||||
if (c == -1) {
|
||||
// EOBException is thrown if no more bytes in buffer. This exception is used to exit the parser when
|
||||
// full packet is not in buffer
|
||||
throw new EOBException();
|
||||
}
|
||||
|
||||
buf[start + i] = (byte) c;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Job to send pump query status packages to all Intelliflo Pump things in order to see the status.
|
||||
* Note: From the internet is seems some FW versions of EasyTouch controllers send this automatically and this the
|
||||
* pump status packets can just be snooped, however my controller version does not do this. No harm in sending.
|
||||
*
|
||||
* @author Jeff James
|
||||
*
|
||||
*/
|
||||
class PumpStatus implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
List<Thing> things = getThing().getThings();
|
||||
|
||||
// FF 00 FF A5 00 60 10 07 00 01 1C
|
||||
byte[] packet = { (byte) 0xA5, (byte) 0x00, (byte) 0x00, (byte) id, (byte) 0x07, (byte) 0x00 };
|
||||
|
||||
PentairPacket p = new PentairPacket(packet);
|
||||
|
||||
for (Thing t : things) {
|
||||
if (!t.getThingTypeUID().equals(INTELLIFLO_THING_TYPE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
p.setDest(((PentairIntelliFloHandler) t.getHandler()).id);
|
||||
writePacket(p);
|
||||
try {
|
||||
Thread.sleep(300); // make sure each pump has time to respond
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
if (handler instanceof PentairControllerHandler controllerHandler) {
|
||||
return controllerHandler;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the thread to read and parse the input stream. Once a packet can be indentified, it locates the
|
||||
* representive sending Thing and dispositions the packet so it can be further processed.
|
||||
*
|
||||
* @author Jeff James - initial implementation
|
||||
*
|
||||
*/
|
||||
class Parser implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
logger.debug("parser thread started");
|
||||
byte[] buf = new byte[40];
|
||||
int c;
|
||||
int chksum, i, length;
|
||||
Thing thing;
|
||||
PentairBaseThingHandler thinghandler;
|
||||
public @Nullable PentairIntelliChlorHandler findIntellichlor() {
|
||||
List<Thing> things = getThing().getThings();
|
||||
|
||||
ParserState parserstate = ParserState.WAIT_SOC;
|
||||
for (Thing t : things) {
|
||||
PentairBaseThingHandler handler = (PentairBaseThingHandler) t.getHandler();
|
||||
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
c = getByte(parserstate);
|
||||
if (handler instanceof PentairIntelliChlorHandler intelliChlorHandler) {
|
||||
return intelliChlorHandler;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
return null;
|
||||
}
|
||||
|
||||
if (c == 0x00) {
|
||||
parserstate = ParserState.CMD_PENTAIR;
|
||||
}
|
||||
@Override
|
||||
public void onPentairPacket(PentairStandardPacket p) {
|
||||
PentairBaseThingHandler thinghandler;
|
||||
|
||||
int source = p.getSource();
|
||||
thinghandler = equipment.get(source);
|
||||
|
||||
if (thinghandler == null) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (c == 0x10) {
|
||||
parserstate = ParserState.CMD_INTELLICHLOR;
|
||||
}
|
||||
break;
|
||||
case CMD_PENTAIR:
|
||||
parserstate = ParserState.WAIT_SOC; // any break will go back to WAIT_SOC
|
||||
|
||||
if (c != 0xFF) {
|
||||
logger.debug("FF00 !FF");
|
||||
break;
|
||||
}
|
||||
|
||||
if (getBytes(buf, 0, 6) != 6) { // read enough to get the length
|
||||
logger.debug("Unable to read 6 bytes");
|
||||
|
||||
break;
|
||||
}
|
||||
if (buf[0] != (byte) 0xA5) {
|
||||
logger.debug("FF00FF !A5");
|
||||
break;
|
||||
}
|
||||
|
||||
length = buf[5];
|
||||
if (length == 0) {
|
||||
logger.debug("Command length of 0");
|
||||
}
|
||||
if (length > 34) {
|
||||
logger.debug("Received packet longer than 34 bytes: {}", length);
|
||||
break;
|
||||
}
|
||||
if (getBytes(buf, 6, length) != length) { // read remaining packet
|
||||
break;
|
||||
}
|
||||
|
||||
chksum = 0;
|
||||
for (i = 0; i < length + 6; i++) {
|
||||
chksum += buf[i] & 0xFF;
|
||||
}
|
||||
|
||||
c = getByte(parserstate) << 8;
|
||||
c += getByte(parserstate);
|
||||
|
||||
if (c != chksum) {
|
||||
logger.debug("Checksum error: {}", PentairPacket.bytesToHex(buf, length + 6));
|
||||
break;
|
||||
}
|
||||
|
||||
PentairPacket p = new PentairPacket(buf);
|
||||
|
||||
thing = findThing(p.getSource());
|
||||
if (thing == null) {
|
||||
if ((p.getSource() >> 8) == 0x02) { // control panels are 0x3*, don't treat as an
|
||||
// unregistered device
|
||||
logger.trace("Command from control panel device ({}): {}", p.getSource(), p);
|
||||
} else if (!unregistered.contains(p.getSource())) { // if not yet seen, print out log
|
||||
// message once
|
||||
logger.info("Command from unregistered device ({}): {}", p.getSource(), p);
|
||||
unregistered.add(p.getSource());
|
||||
} else {
|
||||
logger.trace("Command from unregistered device ({}): {}", p.getSource(), p);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
thinghandler = (PentairBaseThingHandler) thing.getHandler();
|
||||
if (thinghandler == null) {
|
||||
logger.debug("Thing handler = null");
|
||||
break;
|
||||
}
|
||||
|
||||
logger.trace("Received pentair command: {}", p);
|
||||
|
||||
thinghandler.processPacketFrom(p);
|
||||
|
||||
break;
|
||||
case CMD_INTELLICHLOR:
|
||||
parserstate = ParserState.WAIT_SOC;
|
||||
|
||||
buf[0] = 0x10; // 0x10 is included in checksum
|
||||
if (c != (byte) 0x02) {
|
||||
break;
|
||||
}
|
||||
|
||||
buf[1] = 0x2;
|
||||
length = 3;
|
||||
// assume 3 byte command, plus 1 checksum, plus 0x10, 0x03
|
||||
if (getBytes(buf, 2, 6) != 6) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check to see if this is a 3 or 4 byte command
|
||||
if ((buf[6] != (byte) 0x10 || buf[7] != (byte) 0x03)) {
|
||||
length = 4;
|
||||
|
||||
buf[8] = (byte) getByte(parserstate);
|
||||
if ((buf[7] != (byte) 0x10) && (buf[8] != (byte) 0x03)) {
|
||||
logger.debug("Invalid Intellichlor command: {}",
|
||||
PentairPacket.bytesToHex(buf, length + 6));
|
||||
break; // invalid command
|
||||
}
|
||||
}
|
||||
|
||||
chksum = 0;
|
||||
for (i = 0; i < length + 2; i++) {
|
||||
chksum += buf[i] & 0xFF;
|
||||
}
|
||||
|
||||
c = buf[length + 2] & 0xFF;
|
||||
if (c != (chksum & 0xFF)) { // make sure it matches chksum
|
||||
logger.debug("Invalid Intellichlor checksum: {}",
|
||||
PentairPacket.bytesToHex(buf, length + 6));
|
||||
break;
|
||||
}
|
||||
|
||||
PentairPacketIntellichlor pic = new PentairPacketIntellichlor(buf, length);
|
||||
|
||||
thing = findThing(0);
|
||||
|
||||
if (thing == null) {
|
||||
if (!unregistered.contains(0)) { // if not yet seen, print out log message
|
||||
logger.info("Command from unregistered Intelliflow: {}", pic);
|
||||
unregistered.add(0);
|
||||
} else {
|
||||
logger.trace("Command from unregistered Intelliflow: {}", pic);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
thinghandler = (PentairBaseThingHandler) thing.getHandler();
|
||||
if (thinghandler == null) {
|
||||
logger.debug("Thing handler = null");
|
||||
break;
|
||||
}
|
||||
|
||||
thinghandler.processPacketFrom(pic);
|
||||
|
||||
break;
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.trace("I/O error while reading from stream: {}", e.getMessage());
|
||||
disconnect();
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
} catch (EOBException e) {
|
||||
// EOB Exception is used to exit the parser loop if full message is not in buffer.
|
||||
}
|
||||
|
||||
logger.debug("msg reader thread exited");
|
||||
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);
|
||||
actions.ackResponse(p.getAction());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@Override
|
||||
public void onIntelliChlorPacket(PentairIntelliChlorPacket p) {
|
||||
PentairBaseThingHandler thinghandler;
|
||||
|
||||
p.setSource(id);
|
||||
thinghandler = equipment.get(0);
|
||||
|
||||
System.arraycopy(preamble, 0, buf, 0, 5);
|
||||
System.arraycopy(p.buf, 0, buf, 5, p.getLength() + 6);
|
||||
int checksum = p.calcChecksum();
|
||||
if (thinghandler == null) {
|
||||
// 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;
|
||||
|
||||
buf[p.getLength() + 11] = (byte) ((checksum >> 8) & 0xFF);
|
||||
buf[p.getLength() + 12] = (byte) (checksum & 0xFF);
|
||||
if (config.discovery && discoveryService != null) {
|
||||
discoveryService.notifyDiscoveredThing(INTELLICHLOR_THING_TYPE, 0, INTELLICHLOR);
|
||||
|
||||
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());
|
||||
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(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writerFailureCallback() {
|
||||
baseDisconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parserFailureCallback() {
|
||||
baseDisconnect();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
|
||||
return;
|
||||
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 false;
|
||||
} catch (IOException e) {
|
||||
String msg = String.format("cannot open connection to %s", configuration.address);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
|
||||
return;
|
||||
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 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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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:
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
740
bundles/org.openhab.binding.pentair/src/test/data/easytouch8.dat
Normal file
740
bundles/org.openhab.binding.pentair/src/test/data/easytouch8.dat
Normal 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
|
@ -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
|
@ -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
|
@ -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")));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user