From 3fd468236ecee79f3cb03d4630eafb946811f7b0 Mon Sep 17 00:00:00 2001 From: Jeff James Date: Mon, 30 Nov 2020 18:00:28 -0800 Subject: [PATCH] Implemented controller schedules (both read and write) implementation Various other fixes Addressed many warning/errors and cleanup Added support for units on temperatures, power, etc. Added intelliflo gpm fixed spelling error added intelliflo status Added direct support for motor (when controller is not present or in service mode) Removed apache.commons dependency Removed gnu.io dependency. Reworked some of the state changes in the basebridgehandler. Added auto discovery Finished schedule implementation Various other fixes Addressed many warning/errors More cleanup Updated README with changes. Added support for UOM Added intelliflo gpm fixed spelling error added intelliflo status Removed apache.commons import Removed gnu.io dependency. Reworked some of the state changes in the basebridgehandler. Added auto discovery Signed-off-by: Jeff James --- bundles/org.openhab.binding.pentair/README.md | 319 +++-- .../pentair/internal/ExpiringCache.java | 154 +++ .../internal/PentairBindingConstants.java | 156 ++- .../internal/PentairControllerCircuit.java | 307 +++++ .../internal/PentairControllerConstants.java | 68 + .../internal/PentairControllerSchedule.java | 296 +++++ .../internal/PentairControllerStatus.java | 161 +++ .../internal/PentairDiscoveryService.java | 124 ++ .../internal/PentairHandlerFactory.java | 41 +- .../pentair/internal/PentairHeatStatus.java | 99 ++ .../pentair/internal/PentairIntelliChem.java | 249 ++++ .../internal/PentairIntelliChlorPacket.java | 167 +++ .../pentair/internal/PentairPacket.java | 100 +- .../internal/PentairPacketHeatSetPoint.java | 72 -- .../internal/PentairPacketIntellichlor.java | 105 -- .../internal/PentairPacketPumpStatus.java | 89 -- .../pentair/internal/PentairPacketStatus.java | 121 -- .../pentair/internal/PentairParser.java | 239 ++++ .../pentair/internal/PentairPumpStatus.java | 99 ++ .../PentairControllerHandlerConfig.java | 28 + .../config/PentairIPBridgeConfig.java | 17 +- .../PentairIntelliChemHandlerConfig.java | 27 + .../PentairIntelliFloHandlerConfig.java | 27 + .../config/PentairSerialBridgeConfig.java | 14 +- .../handler/PentairBaseBridgeHandler.java | 628 ++++----- .../handler/PentairBaseThingHandler.java | 132 +- .../handler/PentairControllerHandler.java | 1131 +++++++++++++++++ .../handler/PentairEasyTouchHandler.java | 497 -------- .../handler/PentairIPBridgeHandler.java | 77 +- .../handler/PentairIntelliChemHandler.java | 140 ++ .../handler/PentairIntelliChlorHandler.java | 182 ++- .../handler/PentairIntelliFloHandler.java | 457 +++++-- .../handler/PentairSerialBridgeHandler.java | 156 ++- .../resources/OH-INF/thing/controller.xml | 375 ++++++ .../main/resources/OH-INF/thing/easytouch.xml | 136 -- .../resources/OH-INF/thing/intellichem.xml | 120 ++ .../resources/OH-INF/thing/intellichlor.xml | 87 +- .../resources/OH-INF/thing/intelliflo.xml | 62 +- .../main/resources/OH-INF/thing/ip_bridge.xml | 9 +- .../resources/OH-INF/thing/serial_bridge.xml | 10 +- .../src/test/data/easytouch8.dat | 740 +++++++++++ .../src/test/data/easytouch8b.dat | 90 ++ .../src/test/data/nodejs-capture.dat | 276 ++++ .../PentairControllerScheduleTest.java | 115 ++ .../internal/PentairControllerStatusTest.java | 113 ++ .../internal/PentairHeatStatusTest.java | 73 ++ .../internal/PentairIntelliChemTest.java | 78 ++ .../pentair/internal/PentairParserTest.java | 198 +++ .../internal/PentairPumpStatusTest.java | 95 ++ .../pentair/internal/TestUtilities.java | 71 ++ .../handler/PentairControllerHandlerTest.java | 146 +++ .../PentairIntelliChemHandlerTest.java | 123 ++ .../PentairIntelliChlorHandlerTest.java | 153 +++ .../handler/PentairIntelliFloHandlerTest.java | 153 +++ .../src/test/resources/log4j.xml | 17 + .../src/test/resources/logback-test.xml | 45 + .../test/resources/simplelogger.properties | 34 + 57 files changed, 7924 insertions(+), 1874 deletions(-) create mode 100755 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/ExpiringCache.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerCircuit.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerConstants.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerSchedule.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerStatus.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairDiscoveryService.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairHeatStatus.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairIntelliChem.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairIntelliChlorPacket.java delete mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketHeatSetPoint.java delete mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketIntellichlor.java delete mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketPumpStatus.java delete mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketStatus.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairParser.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPumpStatus.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairControllerHandlerConfig.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairIntelliChemHandlerConfig.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairIntelliFloHandlerConfig.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairControllerHandler.java delete mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairEasyTouchHandler.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChemHandler.java create mode 100644 bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/controller.xml delete mode 100644 bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/easytouch.xml create mode 100644 bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/intellichem.xml create mode 100644 bundles/org.openhab.binding.pentair/src/test/data/easytouch8.dat create mode 100644 bundles/org.openhab.binding.pentair/src/test/data/easytouch8b.dat create mode 100644 bundles/org.openhab.binding.pentair/src/test/data/nodejs-capture.dat create mode 100644 bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairControllerScheduleTest.java create mode 100644 bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairControllerStatusTest.java create mode 100644 bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairHeatStatusTest.java create mode 100644 bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairIntelliChemTest.java create mode 100644 bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairParserTest.java create mode 100644 bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairPumpStatusTest.java create mode 100644 bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/TestUtilities.java create mode 100644 bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairControllerHandlerTest.java create mode 100644 bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChemHandlerTest.java create mode 100644 bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChlorHandlerTest.java create mode 100644 bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairIntelliFloHandlerTest.java create mode 100644 bundles/org.openhab.binding.pentair/src/test/resources/log4j.xml create mode 100644 bundles/org.openhab.binding.pentair/src/test/resources/logback-test.xml create mode 100644 bundles/org.openhab.binding.pentair/src/test/resources/simplelogger.properties diff --git a/bundles/org.openhab.binding.pentair/README.md b/bundles/org.openhab.binding.pentair/README.md index 0f3a8ac0635f0..96a46756848f7 100644 --- a/bundles/org.openhab.binding.pentair/README.md +++ b/bundles/org.openhab.binding.pentair/README.md @@ -2,7 +2,16 @@ 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. +The binding was developed and tested on a system with a Pentair EasyTouch controller, but will also operate with the Pentair IntelliTouch 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. + +### Upgrading from OpenHAB 2.x binding + +In addition to many feature and quality improvements, this update also restructures things that will require an updated configuration. Most notably: + +- pentair_serial_bridge has been renamed to just serial_bridge to be consistent with the naming of the ip_bridge. +- EasyThouch controller Thing has been renamed to just Controller since it interfaces to more than just an EasyTouch. +- The Controller thing channels have been organized into groups to make it more readable and logical. ## Hardware Setup @@ -12,29 +21,30 @@ This binding requires an adapter to interface to the Pentair system bus. This bus/wire runs between the Pentair control system, indoor control panels, IntelliFlo pumps, etc. It is a standard RS-485 bus running at 9600,8N1 so any RS-485 adapter should work and you should be able to buy one for under $30. Pentair does not publish any information on the protocol so this binding was developed using the great reverse-engineering efforts of others made available on the internet. -I have cited sevearl of those in the References section below. +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/openhab2, `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,7 +52,7 @@ 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 @@ -52,19 +62,21 @@ This binding supports the following thing types: | --------------- | :--------: | --------------------------------------- | | 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 or IntelliTouch pool controller. | +| IntelliFlo | Thing | Pentair IntelliFlo variable speed pump. | +| IntelliChlor | Thing | Pentair IntelliChlor chlorinator. | +| IntelliChem | Thing | Penaair 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 -Pentair things can be configured either through the online Paper UI configuration, or manually through a 'pentair.thing' configuration file. -The following table shows the available configuration parameters for each thing. +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 | | ------------- | ------------------------------------------------------------ | @@ -76,117 +88,186 @@ The following table shows the available configuration parameters for each thing. | | pollPeriod - Period of time in minutes between the poll command being sent to the IT-100 bridge - Not Required - default=1. | | | id - ID to use when communciating on Pentair control bus - default = 34. | -Currently automatic discovery is not supported and the binding requires configuration via the Paper UI or a file in the conf/things folder. -Here is an example of a thing configuration file called 'pentair.things': + +Here is an example of a thing configuration file called 'pentair.things' for using the ip_bridge: ``` 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': + ``` 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 - -Pentair things support a variety of channels as seen below in the following table: - -| Channel | Item Type | Description | -| -------------------- | --------- | ------------------------------------------------------------ | -| EasyTouch Controller | | | -| pooltemp | Number | Current pool temperature (readonly) | -| spatemp | Number | Current spa temperature (readonly) | -| airtemp | Number | Current air temperature (readonly) | -| solartemp | Number | Current solar temperature (readonly) | -| poolheatmode | Number | Current heat mode setting for pool (readonly): 0=Off, 1=Heater, 2=Solar Preferred, 3=Solar | -| poolheatmodestr | String | Current heat mode setting for pool in string form (readonly) | -| spaheatmode | Number | Current heat mode setting for spa (readonly): 0=Off, 1=Heater, 2=Solar Preferred, 3=Solar | -| spaheatmodestr | String | Current heat mode setting for spa in string form (readonly)> | -| poolsetpoint | Number | Current pool temperature set point | -| spasetpoint | Number | Current spa temperature set point | -| heatactive | Number | Heater mode is active | -| pool | Switch | Primary pool mode | -| spa | Switch | Spa mode | -| aux1 | Switch | Aux1 mode | -| aux2 | Switch | Aux2 mode | -| aux3 | Switch | Aux3 mode | -| aux4 | Switch | Aux4 mode | -| aux5 | Switch | Aux5 mode | -| aux6 | Switch | Aux6 mode | -| aux7 | Switch | Aux7 mode | -| feature1 | Switch | Feature1 mode | -| feature2 | Switch | Feature2 mode | -| feature3 | Switch | Feature3 mode | -| feature4 | Switch | Feature4 mode | -| feature5 | Switch | Feature5 mode | -| feature6 | Switch | Feature6 mode | -| feature7 | Switch | Feature7 mode | -| feature8 | Switch | Feature8 mode | -| IntelliChlor | | | -| saltoutput | Number | Current salt output % (readonly) | -| salinity | Number | Salinity (ppm) (readonly) | -| IntelliFlo Pump | | | -| run | Number | Pump running (readonly) | -| drivestate | Number | Pump drivestate (readonly) | -| mode | Number | Pump mode (readonly) | -| rpm | Number | Pump RPM (readonly) | -| power | Number | Pump power in Watts (readonly) | -| error | Number | Pump error (readonly) | -| ppc | Number | Pump PPC? (readonly) | - -## Full Example - -The following is an example of an item file (pentair.items), you can change the °F to °C if you are using metric temperature units: +## Things & Channels + +### Thing: Controller + +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. + +#### Synchronize Time + +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 | RW | Time of day to start schedule expressed in minutes. | +| " | end | Number | 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. | +| " | uom | String | R | Unit of measure. Values: CELCIUS, FARENHEIT. | +| " | 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 | R | Current salt output %. | +| salinity | Number | 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 | R | Pump GPM (only valid for VF pumps) | +| power | Number:Power | R | Pump power (Watt) | +| status1 | Number | R | Pump status1. | +| status2 | Number | R | Pump status2. | +| program1 | Switch | RW | Run pump program 1 settings. | +| program2 | Switch | RW | Run pump program 2 settings. | +| program3 | Switch | RW | Run pump program 3 settings. | +| program4 | Switch | RW | Run pump program 4 settings. | + + +### Thing: IntelliChem + +Represents and interfaces to an IntelliChem unit. This is for montioring of values only and IntelliChem cannot be directly controlled through this binding. + +| 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. | +| tank1 | Number | R | Tank 1 level. | +| tank2 | Number | R | Tank 2 level. | +| calciumhardness | Number | R | Calcium hardness PPM (mg/L). | +| cyareading | Number | R | Cyanuric acid reading. | +| totalalkalinity | Number | R | Total Alkalinity reading. | +| waterflowalarm | Switch | R | Water flow alarm (on = no water flow). | +| mode1 | Number | R | Mode 1 status. | +| mode2 | Number | R | Mode 2 status. | +| saturationindex | Number | R | Calculated saturation index. | + + +## Example setup + +### pentair.items ``` -Group gPool "Pool" - -Number Pool_Temp "Pool Temp [%.1f °F]" (gPool) { channel = "pentair:easytouch:1:main:pooltemp" } -Number Spa_Temp "Spa Temp [%.1f °F]" (gPool) { channel = "pentair:easytouch:1:main:spatemp" } -Number Air_Temp "Air Temp [%.1f °F]" (gPool) { channel = "pentair:easytouch:1:main:airtemp" } -Number Solar_Temp "Solar Temp [%.1f °F]" (gPool) { channel = "pentair:easytouch:1:main:solartemp" } - -Number PoolHeatMode "Pool Heat Mode [%d]" (gPool) { channel="pentair:easytouch:1:main:poolheatmode" } -String PoolHeatModeStr "Pool Heat Mode [%s]" (gPool) { channel="pentair:easytouch:1:main:poolheatmodestr" } -Number SpaHeatMode "Spa Heat Mode [%d]" (gPool) { channel="pentair:easytouch:1:main:spaheatmode" } -String SpaHeatModeStr "Spa Heat Mode [%s]" (gPool) { channel="pentair:easytouch:1:main:spaheatmodestr" } -Number PoolSetPoint "Pool Set Point [%.1f °F]" (gPool) { channel="pentair:easytouch:1:main:poolsetpoint" } -Number SpaSetPoint "Spa Set Point [%.1f °F]" (gPool) { channel="pentair:easytouch:1:main:spasetpoint" } -Number HeatActive "Heat Active [%d]" (gPool) { channel="pentair:easytouch:1:main:heatactive" } - -Switch Mode_Spa "Spa Mode" (gPool) { channel = "pentair:easytouch:1:main:spa" } -Switch Mode_Pool "Pool Mode" (gPool) { channel = "pentair:easytouch:1:main:pool" } -Switch Mode_PoolLight "Pool Light" (gPool) { channel = "pentair:easytouch:1:main:aux1" } -Switch Mode_SpaLight "Spa Light" (gPool) { channel = "pentair:easytouch:1:main:aux2" } -Switch Mode_Jets "Jets" (gPool) { channel = "pentair:easytouch:1:main:aux3" } -Switch Mode_Boost "Boost Mode" (gPool) { channel = "pentair:easytouch:1:main:aux4" } -Switch Mode_Aux5 "Aux5 Mode" (gPool) { channel = "pentair:easytouch:1:main:aux5" } -Switch Mode_Aux6 "Aux6 Mode" (gPool) { channel = "pentair:easytouch:1:main:aux6" } -Switch Mode_Aux7 "Aux7 Mode" (gPool) { channel = "pentair:easytouch:1:main:aux7" } -Switch Mode_Spillway "Spillway Mode" (gPool) { channel = "pentair:easytouch:1:main:feature1" } - -Number SaltOutput "Salt Output [%d%%]" (gPool) { channel = "pentair:intellichlor:1:ic40:saltoutput" } -Number Salinity "Salinity [%d ppm]" (gPool) { channel = "pentair:intellichlor:1:ic40:salinity" } - -Switch Pump_Run "Pump running [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:run" } -Number Pump_DriveState "Pump drivestate [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:drivestate" } -Number Pump_Mode "Pump Mode [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:mode" } -Number Pump_RPM "Pump RPM [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:rpm" } -Number Pump_Power "Pump Power [%d W]" (gPool) { channel = "pentair:intelliflo:1:pump1:power" } -Number Pump_Error "Pump Error [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:error" } -Number Pump_PPC "Pump PPC [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:ppc" } + +Group gPool (All) + +Number:Temperature Pool_Temp "Pool Temperature" (gPool) { channel = "pentair:controller:1:main:poolheat#temperature" } +Number:Temperature Spa_Temp "Spa Temperature " (gPool) { channel = "pentair:controller:1:main:spaheat#temperature" } +Number:Temperature Air_Temp "Air Temperature" (gPool) { channel = "pentair:controller:1:main:status#airtemperature" } +Number:Temperature Solar_Temp "Solar Temperature" (gPool) { channel = "pentair:controller:1:main:status#solartemperature" } + +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" } + +String PoolLightMode "Light Mode" (gPool) { channel="pentair:controller:1:main:status#lightmode" } + +Number PoolHeatEnable "Pool Heat Enable [%d]" (gPool) { channel="pentair:controller:1:main:poolheatenable" } +Number SpaHeatEnable "Spa Heat Enable [%d]" (gPool) { channel="pentair:controller:1:main:spaheatenable" } + +Switch Mode_Pool "Pool Mode" (gPool) { channel = "pentair:controller:1:main:pool#switch" } +Switch Mode_Spa "Spa" (gPool) { channel = "pentair:controller:1:main:spa#switch" } +Switch Mode_PoolLight "Pool Light" (gPool) { channel = "pentair:controller:1:main:aux1#switch" } +Switch Mode_SpaLight "Spa Light" (gPool) { channel = "pentair:controller:1:main:aux2#switch" } +Switch Mode_Jets "Jets" (gPool) { channel = "pentair:controller:1:main:aux3#switch" } +Switch Mode_Boost "Boost Mode" (gPool) { channel = "pentair:controller:1:main:aux4#switch" } +Switch Mode_Aux5 "Aux5 Mode" (gPool) { channel = "pentair:controller:1:main:aux5#switch" } +Switch Mode_Aux6 "Aux6 Mode" (gPool) { channel = "pentair:controller:1:main:aux6#switch" } +Switch Mode_Aux7 "Aux7 Mode" (gPool) { channel = "pentair:controller:1:main:aux7#switch" } + +String ModeName_Pool "Pool Name [%s]" (gPool) { channel = "pentair:controller:1:main:pool#name" } +String ModeFunc_Pool "Pool Func [%s]" (gPool) { channel = "pentair:controller:1:main:pool#function" } + +String ModeName_Spa "Spa Name [%s]" (gPool) { channel = "pentair:controller:1:main:spa#name" } +String ModeFunc_Spa "Spa Func [%s]" (gPool) { channel = "pentair:controller:1:main:spa#function" } + +Switch Delay "Heater Delay" (gPool) { channel = "pentair:controller:1:main:status#heaterdelay" } + +Number Salt_Output "Salt Output [%d%%]" (gPool) { channel = "pentair:intellichlor:1:ic40:salt_output" } +Number Salinity "Salinity [%d ppm]" (gPool) { channel = "pentair:intellichlor:1:ic40:salinity" } + +Switch Pump_Run "Pump 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 + ``` sitemap pool label="Pool stuff" { Frame label="Pool" { @@ -194,13 +275,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 } } @@ -210,10 +292,21 @@ sitemap pool label="Pool stuff" { 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 + +## Updates in 2.5.7 + +Added automotic discovery of devices +EasyTouch thing has been renamed to a more generic Controller +Controller makes liberal use of channel groups to better organize channels +Added support for reading and writing Controller schedules +Added support for synchronizing the controller time +Added support for direct control of Intelliflo pumps +Added support for IntelliBrite color selection +Added support for UOM for temperature and pump power. +Improved robustness of communication on RS485 bus +Move serial implementation to openhab-transport-serial from gnu.io +Added unit testing ## 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. diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/ExpiringCache.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/ExpiringCache.java new file mode 100755 index 0000000000000..3f9f8b4f9aad7 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/ExpiringCache.java @@ -0,0 +1,154 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import java.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 + * + * @param the type of the value + */ +@NonNullByDefault +public class ExpiringCache { + 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) { + 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; + } +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairBindingConstants.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairBindingConstants.java index 90b78e3e9b468..e4a83dd7eae2c 100644 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairBindingConstants.java +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairBindingConstants.java @@ -12,10 +12,7 @@ */ 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 +33,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,54 +44,117 @@ 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); - - // List of all Channel ids - public static final String EASYTOUCH_POOLTEMP = "pooltemp"; - public static final String EASYTOUCH_SPATEMP = "spatemp"; - public static final String EASYTOUCH_AIRTEMP = "airtemp"; - public static final String EASYTOUCH_SOLARTEMP = "solartemp"; - - public static final String EASYTOUCH_SPAHEATMODE = "spaheatmode"; - public static final String EASYTOUCH_SPAHEATMODESTR = "spaheatmodestr"; - public static final String EASYTOUCH_POOLHEATMODE = "poolheatmode"; - public static final String EASYTOUCH_POOLHEATMODESTR = "poolheatmodestr"; - public static final String EASYTOUCH_HEATACTIVE = "heatactive"; - - public static final String EASYTOUCH_POOLSETPOINT = "poolsetpoint"; - public static final String EASYTOUCH_SPASETPOINT = "spasetpoint"; - - public static final String EASYTOUCH_POOL = "pool"; - public static final String EASYTOUCH_SPA = "spa"; - public static final String EASYTOUCH_AUX1 = "aux1"; - public static final String EASYTOUCH_AUX2 = "aux2"; - public static final String EASYTOUCH_AUX3 = "aux3"; - public static final String EASYTOUCH_AUX4 = "aux4"; - public static final String EASYTOUCH_AUX5 = "aux5"; - public static final String EASYTOUCH_AUX6 = "aux6"; - public static final String EASYTOUCH_AUX7 = "aux7"; - - public static final String EASYTOUCH_FEATURE1 = "feature1"; - public static final String EASYTOUCH_FEATURE2 = "feature2"; - public static final String EASYTOUCH_FEATURE3 = "feature3"; - public static final String EASYTOUCH_FEATURE4 = "feature4"; - public static final String EASYTOUCH_FEATURE5 = "feature5"; - public static final String EASYTOUCH_FEATURE6 = "feature6"; - public static final String EASYTOUCH_FEATURE7 = "feature7"; - public static final String EASYTOUCH_FEATURE8 = "feature8"; + public static final ThingTypeUID INTELLICHEM_THING_TYPE = new ThingTypeUID(BINDING_ID, INTELLICHEM); + + public static final String PARAMETER_ID = "id"; + + // Controller Groups and Items + + public static final String CONTROLLER_PROPERTYFWVERSION = "fwversion"; + public static final String CONTROLLER_PROPERTYID = "id"; + + public static final String CONTROLLER_STATUS = "status"; + + public static final String CONTROLLER_AIRTEMPERATURE = "airtemperature"; + public static final String CONTROLLER_SOLARTEMPERATURE = "solartemperature"; + public static final String CONTROLLER_LIGHTMODE = "lightmode"; + public static final String CONTROLLER_UOM = "uom"; + public static final String CONTROLLER_SERVICEMODE = "servicemode"; + public static final String CONTROLLER_SOLARON = "solaron"; + public static final String CONTROLLER_HEATERON = "heateron"; + public static final String CONTROLLER_HEATERDELAY = "heaterdelay"; + + public static final String CONTROLLER_POOLCIRCUIT = "pool"; + public static final String CONTROLLER_SPACIRCUIT = "spa"; + public static final String CONTROLLER_AUX1CIRCUIT = "aux1"; + public static final String CONTROLLER_AUX2CIRCUIT = "aux2"; + public static final String CONTROLLER_AUX3CIRCUIT = "aux3"; + public static final String CONTROLLER_AUX4CIRCUIT = "aux4"; + public static final String CONTROLLER_AUX5CIRCUIT = "aux5"; + public static final String CONTROLLER_AUX6CIRCUIT = "aux6"; + public static final String CONTROLLER_AUX7CIRCUIT = "aux7"; + public static final String CONTROLLER_AUX8CIRCUIT = "aux8"; + + public static final String CONTROLLER_CIRCUITSWITCH = "switch"; + public static final String CONTROLLER_CIRCUITNAME = "name"; + public static final String CONTROLLER_CIRCUITFUNCTION = "function"; + + public static final String CONTROLLER_FEATURE1 = "feature1"; + public static final String CONTROLLER_FEATURE2 = "feature2"; + public static final String CONTROLLER_FEATURE3 = "feature3"; + public static final String CONTROLLER_FEATURE4 = "feature4"; + public static final String CONTROLLER_FEATURE5 = "feature5"; + public static final String CONTROLLER_FEATURE6 = "feature6"; + public static final String CONTROLLER_FEATURE7 = "feature7"; + public static final String CONTROLLER_FEATURE8 = "feature8"; + + public static final String CONTROLLER_FEATURESWITCH = "switch"; + + // List of heat group and items + public static final String CONTROLLER_POOLHEAT = "poolheat"; + public static final String CONTROLLER_SPAHEAT = "spaheat"; + + public static final String CONTROLLER_TEMPERATURE = "temperature"; + public static final String CONTROLLER_SETPOINT = "setpoint"; + public static final String CONTROLLER_HEATMODE = "heatmode"; + + // List of schedule group and items + public static final String CONTROLLER_SCHEDULE = "schedule%d"; + + public static final String CONTROLLER_SCHEDULESAVE = "save"; + public static final String CONTROLLER_SCHEDULESTRING = "schedule"; + public static final String CONTROLLER_SCHEDULETYPE = "type"; + public static final String CONTROLLER_SCHEDULECIRCUIT = "circuit"; + public static final String CONTROLLER_SCHEDULEDAYS = "days"; + public static final String CONTROLLER_SCHEDULESTART = "start"; + public static final String CONTROLLER_SCHEDULEEND = "end"; + + // List of Intellichlor channel ids + public static final String INTELLICHLOR_PROPERTYVERSION = "version"; + public static final String INTELLICHLOR_PROPERTYMODEL = "model"; public static final String INTELLICHLOR_SALTOUTPUT = "saltoutput"; public static final String INTELLICHLOR_SALINITY = "salinity"; - + public static final String INTELLICHLOR_OK = "ok"; + public static final String INTELLICHLOR_LOWFLOW = "lowflow"; + public static final String INTELLICHLOR_LOWSALT = "lowsalt"; + public static final String INTELLICHLOR_VERYLOWSALT = "verylowsalt"; + public static final String INTELLICHLOR_HIGHCURRENT = "highcurrent"; + public static final String INTELLICHLOR_CLEANCELL = "cleancell"; + public static final String INTELLICHLOR_LOWVOLTAGE = "lowvoltage"; + public static final String INTELLICHLOR_LOWWATERTEMP = "lowwatertemp"; + public static final String INTELLICHLOR_COMMERROR = "commerror"; + + // List of Intellichem channel ids + public static final String INTELLICHEM_PHREADING = "phreading"; + public static final String INTELLICHEM_ORPREADING = "orpreading"; + public static final String INTELLICHEM_PHSETPOINT = "phsetpoint"; + public static final String INTELLICHEM_ORPSETPOINT = "orpsetpoint"; + public static final String INTELLICHEM_TANK1 = "tank1"; + public static final String INTELLICHEM_TANK2 = "tank2"; + public static final String INTELLICHEM_CALCIUMHARDNESS = "calciumhardness"; + public static final String INTELLICHEM_CYAREADING = "cyareading"; + public static final String INTELLICHEM_TOTALALKALINITY = "totalalkalinity"; + public static final String INTELLICHEM_WATERFLOWALARM = "waterflowalarm"; + public static final String INTELLICHEM_MODE1 = "mode1"; + public static final String INTELLICHEM_MODE2 = "mode2"; + public static final String INTELLICHEM_SATURATIONINDEX = "saturationindex"; + + // List of all Intelliflo channel ids public static final String INTELLIFLO_RUN = "run"; - public static final String INTELLIFLO_MODE = "mode"; - public static final String INTELLIFLO_DRIVESTATE = "drivestate"; public static final String INTELLIFLO_POWER = "power"; public static final String INTELLIFLO_RPM = "rpm"; - public static final String INTELLIFLO_PPC = "ppc"; + public static final String INTELLIFLO_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_PROGRAM1 = "program1"; + public static final String INTELLIFLO_PROGRAM2 = "program2"; + public static final String INTELLIFLO_PROGRAM3 = "program3"; + public static final String INTELLIFLO_PROGRAM4 = "program4"; public static final String DIAG = "diag"; @@ -102,7 +163,10 @@ public class PentairBindingConstants { public static final Integer PROPERTY_PORT = 10000; // Set of all supported Thing Type UIDs - public static final Set 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 Set 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); + + public static final Set DISCOVERABLE_DEVICE_TYPE_UIDS = Set.of(CONTROLLER_THING_TYPE, + INTELLIFLO_THING_TYPE, INTELLICHLOR_THING_TYPE, INTELLICHEM_THING_TYPE); } diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerCircuit.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerCircuit.java new file mode 100644 index 0000000000000..f6e5ed974d51e --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerCircuit.java @@ -0,0 +1,307 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import static org.openhab.binding.pentair.internal.PentairBindingConstants.*; + +import java.util.Arrays; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Class to manage Controller Circuits/Features + * + * @author Jeff James - initial contribution + * + */ +@NonNullByDefault +public class PentairControllerCircuit { + public enum CircuitName { + 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 int number; + private String friendlyName; + + private CircuitName(int n, String friendlyName) { + this.number = n; + this.friendlyName = friendlyName; + } + + public int getCode() { + return number; + } + + public String getFriendlyName() { + return friendlyName; + } + + public static @Nullable CircuitName valueOfModeNumber(int number) { + return Arrays.stream(values()).filter(value -> (value.getCode() == number)).findFirst().orElse(null); + } + } + + public enum CircuitFunction { + 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 int code; + private 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 @Nullable CircuitFunction valueOfModeNumber(int number) { + return Arrays.stream(values()).filter(value -> (value.getCode() == number)).findFirst().orElse(null); + } + } + + public enum CircuitGroup { + SPA(1, CONTROLLER_SPACIRCUIT), + AUX1(2, CONTROLLER_AUX1CIRCUIT), + AUX2(3, CONTROLLER_AUX2CIRCUIT), + AUX3(4, CONTROLLER_AUX3CIRCUIT), + AUX4(5, CONTROLLER_AUX4CIRCUIT), + POOL(6, CONTROLLER_POOLCIRCUIT), + AUX5(7, CONTROLLER_AUX5CIRCUIT), + AUX6(8, CONTROLLER_AUX6CIRCUIT), + AUX7(9, CONTROLLER_AUX7CIRCUIT), + AUX8(10, CONTROLLER_AUX8CIRCUIT), + FEATURE1(11, CONTROLLER_FEATURE1), + FEATURE2(12, CONTROLLER_FEATURE2), + FEATURE3(13, CONTROLLER_FEATURE3), + FEATURE4(14, CONTROLLER_FEATURE4), + FEATURE5(15, CONTROLLER_FEATURE5), + FEATURE6(16, CONTROLLER_FEATURE6), + FEATURE7(17, CONTROLLER_FEATURE7), + FEATURE8(18, CONTROLLER_FEATURE8); + + private int number; + private String groupID; + + private CircuitGroup(int n, String groupID) { + this.number = n; + this.groupID = groupID; + } + + public int getNumber() { + return number; + } + + public String getGroupID() { + return groupID; + } + + public static @Nullable CircuitGroup valueOfNumber(int number) { + return Arrays.stream(values()).filter(value -> (value.getNumber() == number)).findFirst().orElse(null); + } + + public static @Nullable CircuitGroup valueOfGroupID(String groupID) { + return Arrays.stream(values()).filter(value -> (value.getGroupID().equals(groupID))).findFirst() + .orElse(null); + } + } + + public final int id; + private final String channelID; + public @Nullable CircuitName circuitName; + public @Nullable CircuitFunction circuitFunction; + + public PentairControllerCircuit(int id, String channelID) { + this.id = id; + this.channelID = channelID; + } + + 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; + } + + public @Nullable CircuitName getNameStr() { + return circuitName; + } + + public @Nullable CircuitFunction getFunction() { + return circuitFunction; + } + + public String getGroupID() { + return channelID; + } + + public static int getCircuitNumberByGroupID(String groupID) { + CircuitGroup circuitGroup = CircuitGroup.valueOfGroupID(groupID); + + if (circuitGroup == null) { + return -1; + } + + return circuitGroup.number; + } + + public static @Nullable String getCircuitGroupIDByNumber(int num) { + CircuitGroup circuitGroup = CircuitGroup.valueOfNumber(num); + + if (circuitGroup == null) { + return null; + } + + return circuitGroup.getGroupID(); + } +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerConstants.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerConstants.java new file mode 100644 index 0000000000000..6b9bcf6374960 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerConstants.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Constants used for the Controller class + * + * @author Jeff James - Initial contribution + */ +@NonNullByDefault +public class PentairControllerConstants { + public enum LightMode { + 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 int number; + private String name; + + private LightMode(int n, String name) { + this.number = n; + this.name = name; + } + + public int getModeNumber() { + return number; + } + + public String getName() { + return name; + } + + public static @Nullable LightMode valueOfModeNumber(int value) { + for (LightMode lightMode : values()) { + if (lightMode.getModeNumber() == value) { + return lightMode; + } + } + return null; + } + } +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerSchedule.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerSchedule.java new file mode 100644 index 0000000000000..97db55e010a75 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerSchedule.java @@ -0,0 +1,296 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import static org.openhab.binding.pentair.internal.PentairBindingConstants.CONTROLLER_SCHEDULE; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Class for the pentair controller schedules. + * + * @author Jeff James - initial contribution + * + */ +@NonNullByDefault +public class PentairControllerSchedule { + public static final int ID = 0; + public static final int CIRCUIT = 1; + public static final int STARTH = 2; + public static final int STARTM = 3; + public static final int ENDH = 4; + public static final int ENDM = 5; + public static final int DAYS = 6; + + private boolean dirty; + + public enum ScheduleType { + NONE("None"), + NORMAL("Normal"), + EGGTIMER("Egg Timer"), + ONCEONLY("Once Only"), + UNKNOWN("Unknown"); + + private 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 boolean isDirty() { + return dirty; + } + + public void setDirty(boolean d) { + dirty = d; + } + + public void parsePacket(PentairPacket p) { + id = p.getByte(ID); + circuit = p.getByte(CIRCUIT); + days = p.getByte(DAYS); + + if (p.getByte(STARTH) == 25) { + type = ScheduleType.EGGTIMER; + start = 0; + end = p.getByte(ENDH) * 60 + p.getByte(ENDM); + } else if (p.getByte(ENDH) == 26) { + type = ScheduleType.ONCEONLY; + start = p.getByte(STARTH) * 60 + p.getByte(STARTM); + end = 0; + } else if (circuit == 0) { + type = ScheduleType.NONE; + start = 0; + end = 0; + } else { + type = ScheduleType.NORMAL; + start = p.getByte(STARTH) * 60 + p.getByte(STARTM); + end = p.getByte(ENDH) * 60 + p.getByte(ENDM); + } + } + + public @Nullable String getScheduleTypeStr() { + String str = type.getName(); + + return str; + } + + public boolean setScheduleCircuit(int c) { + if (circuit == c) { + return true; + } + + if (c > 18 || c <= 0) { + return false; + } + + circuit = c; + dirty = true; + + return true; + } + + public boolean setScheduleStart(int min) { + if (min == start) { + return true; + } + + if (min > 1440 || min < 0) { + return false; + } + + start = min; + dirty = true; + + return true; + } + + public boolean setScheduleEnd(int min) { + if (min == end) { + return true; + } + + if (min > 1440 || min < 0) { + return false; + } + + end = min; + dirty = true; + + return true; + } + + public boolean setScheduleType(ScheduleType type) { + if (this.type == type) { + return true; + } + + this.type = type; + 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) { + 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 PentairPacket getWritePacket(int controllerid, int preamble) { + byte[] packet = { (byte) 0xA5, (byte) preamble, (byte) controllerid, (byte) 0x00 /* source */, (byte) 0x91, + (byte) 7, (byte) id, (byte) circuit, (byte) (start / 60), (byte) (start % 60), (byte) (end / 60), + (byte) (end % 60), (byte) days }; + PentairPacket p = new PentairPacket(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() { + 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) { + final String rePattern = "^(NONE|NORMAL|EGGTIMER|ONCEONLY),(\\\\d+),(\\\\d+):(\\\\d+),(\\\\d+):(\\\\d+),([SMTWRFY]+)"; + + String schedulestr = str.toUpperCase(); + + Pattern ptn = Pattern.compile(rePattern); + Matcher m = ptn.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 = String.format(CONTROLLER_SCHEDULE, id); + + return groupID; + } +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerStatus.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerStatus.java new file mode 100644 index 0000000000000..7cea4f2d5a150 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairControllerStatus.java @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Pentair status packet specialation of a PentairPacket. Includes public variables for many of the reverse engineered + * packet content. + * + * @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; + + protected static final int HOUR = 0; + protected static final int MIN = 1; + protected static final int EQUIP1 = 2; + protected static final int EQUIP2 = 3; + protected static final int EQUIP3 = 4; + protected static final int STATUS = 9; // Celsius (0x04) or Farenheit, Service Mode (0x01) + protected static final int HEAT_ACTIVE = 10; + protected static final int HEATER_DELAY = 12; // Something to do with heat? + protected static final int POOL_TEMP = 14; + protected static final int SPA_TEMP = 15; + protected static final int AIR_TEMP = 18; + protected static final int SOLAR_TEMP = 19; + + // Heat mode defines + protected static final int HEATMODE_OFF = 0; + protected static final int HEATMODE_HEATER = 1; + protected static final int HEATMODE_SOLARPREF = 2; + protected static final int HEATMODE_SOLARONLY = 3; + + public int hour; + public int min; + + /** Individual boolean values representing whether a particular ciruit is on or off */ + protected int equip; + public boolean pool, spa; + public boolean[] circuits = new boolean[NUMCIRCUITS]; + + /** Unit of Measure - Celsius = true, Farenheit = false */ + public boolean uom; + public boolean serviceMode; + public boolean heaterOn; + public boolean solarOn; + public boolean heaterDelay; + + /** 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; + + /** used to store packet value for reverse engineering, not used in normal operation */ + public int diag; + + public void parsePacket(PentairPacket p) { + if (p.getLength() != 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; + 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) ? "C" : "F", serviceMode, heaterDelay); + + return str; + } + + @Override + public boolean equals(@Nullable Object object) { + if (!(object instanceof PentairControllerStatus)) { + return false; + } + + PentairControllerStatus p = (PentairControllerStatus) object; + + for (int i = 0; i < NUMCIRCUITS; i++) { + if (circuits[i] != p.circuits[i]) { + return false; + } + } + + if (poolTemp != p.poolTemp || spaTemp != p.spaTemp || airTemp != p.airTemp || solarTemp != p.solarTemp) { + return false; + } + + if (uom != p.uom || serviceMode != p.serviceMode || solarOn != p.solarOn || heaterOn != p.heaterOn + || heaterDelay != p.heaterDelay) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return Objects.hash(circuits, poolTemp, spaTemp, airTemp, solarTemp, uom, serviceMode, solarOn, heaterOn, + heaterDelay); + } +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairDiscoveryService.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairDiscoveryService.java new file mode 100644 index 0000000000000..0399c8f8c23af --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairDiscoveryService.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import static org.openhab.binding.pentair.internal.PentairBindingConstants.*; + +import java.util.Objects; + +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.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. (Leveraged from + * AlarmDecoder) + * + * @author Jeff James - Initial contribution + */ +@NonNullByDefault +public class PentairDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService { + + private final Logger logger = LoggerFactory.getLogger(PentairDiscoveryService.class); + + private @Nullable PentairBaseBridgeHandler bridgeHandler; + + public PentairDiscoveryService() throws IllegalArgumentException { + super(DISCOVERABLE_DEVICE_TYPE_UIDS, 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 notifyDiscoveredController(int id) { + Objects.requireNonNull(bridgeHandler, "Discovery with null bridgehandler."); + ThingUID bridgeUID = bridgeHandler.getThing().getUID(); + ThingUID uid = new ThingUID(CONTROLLER_THING_TYPE, bridgeUID, CONTROLLER); + + DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withProperty(PARAMETER_ID, id) + .withLabel("Controller").withRepresentationProperty(CONTROLLER_PROPERTYID).build(); + thingDiscovered(result); + logger.debug("Discovered Controller {}", uid); + } + + public void notifyDiscoverdIntelliflo(int id) { + int pumpid = (id & 0x04) + 1; + + Objects.requireNonNull(bridgeHandler, "Discovery with null bridgehandler."); + ThingUID bridgeUID = bridgeHandler.getThing().getUID(); + ThingUID uid = new ThingUID(INTELLIFLO_THING_TYPE, bridgeUID, "pump" + pumpid); + + DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withProperty(PARAMETER_ID, id) + .withLabel("Pump").withRepresentationProperty(CONTROLLER_PROPERTYID).build(); + thingDiscovered(result); + logger.debug("Discovered Pump {}", uid); + } + + public void notifyDiscoveredIntellichlor(int id) { + Objects.requireNonNull(bridgeHandler, "Discovery with null bridgehandler."); + ThingUID bridgeUID = bridgeHandler.getThing().getUID(); + ThingUID uid = new ThingUID(INTELLICHLOR_THING_TYPE, bridgeUID, INTELLICHLOR); + + DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withProperty(PARAMETER_ID, id) + .withLabel("Intellichlor").withRepresentationProperty(CONTROLLER_PROPERTYID).build(); + thingDiscovered(result); + logger.debug("Discovered Intellichlor {}", uid); + } + + public void notifyDiscoveryIntellichem(int id) { + Objects.requireNonNull(bridgeHandler, "Discovery with null bridgehandler."); + ThingUID bridgeUID = bridgeHandler.getThing().getUID(); + ThingUID uid = new ThingUID(INTELLICHEM_THING_TYPE, bridgeUID, INTELLICHEM); + + DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withProperty(PARAMETER_ID, id) + .withLabel("IntelliChem").withRepresentationProperty(CONTROLLER_PROPERTYID).build(); + thingDiscovered(result); + logger.debug("Discovered Intellichem {}", uid); + } + + @Override + public void setThingHandler(ThingHandler handler) { + if (handler instanceof PentairBaseBridgeHandler) { + this.bridgeHandler = (PentairBaseBridgeHandler) handler; + this.bridgeHandler.setDiscoveryService(this); + } else { + this.bridgeHandler = null; + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return this.bridgeHandler; + } +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairHandlerFactory.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairHandlerFactory.java index fcc5e7dd95f7e..1f76f176e8264 100644 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairHandlerFactory.java +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairHandlerFactory.java @@ -14,18 +14,26 @@ import static org.openhab.binding.pentair.internal.PentairBindingConstants.*; -import org.openhab.binding.pentair.internal.handler.PentairEasyTouchHandler; +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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The {@link PentairHandlerFactory} is responsible for creating things and thing @@ -33,27 +41,48 @@ * * @author Jeff James - Initial contribution */ + +@NonNullByDefault @Component(service = ThingHandlerFactory.class, configurationPid = "binding.pentair") public class PentairHandlerFactory extends BaseThingHandlerFactory { + + private final Logger logger = LoggerFactory.getLogger(PentairHandlerFactory.class); + 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); + PentairIPBridgeHandler bridgeHandler = new PentairIPBridgeHandler((Bridge) thing); + + // registerDiscoveryService(bridgeHandler); + return bridgeHandler; } else if (thingTypeUID.equals(SERIAL_BRIDGE_THING_TYPE)) { - return new PentairSerialBridgeHandler((Bridge) thing); - } else if (thingTypeUID.equals(EASYTOUCH_THING_TYPE)) { - return new PentairEasyTouchHandler(thing); + PentairSerialBridgeHandler bridgeHandler = new PentairSerialBridgeHandler((Bridge) thing, + serialPortManager); + + // registerDiscoveryService(bridgeHandler); + return bridgeHandler; + } 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; diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairHeatStatus.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairHeatStatus.java new file mode 100644 index 0000000000000..e7c44fc62260c --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairHeatStatus.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import java.util.Arrays; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * 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 + * + */ +@NonNullByDefault +public class PentairHeatStatus { + + public enum HeatMode { + NONE(0, "None"), + HEATER(1, "Heater"), + SOLARPREFERRED(2, "Solar Preferred"), + SOLAR(3, "Solar"); + + private int code; + private 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 @Nullable HeatMode valueOfCode(int code) { + return Arrays.stream(values()).filter(value -> (value.getCode() == code)).findFirst().orElse(null); + } + } + + protected static final int POOLTEMP = 1; + protected static final int AIRTEMP = 2; + protected static final int POOLSETPOINT = 3; + protected static final int SPASETPOINT = 4; + protected static final int HEATMODE = 5; + protected static final int SOLARTEMP = 8; + + /** pool temperature set point */ + public int poolSetPoint; + /** pool heat mode - 0=Off, 1=Heater, 2=Solar Pref, 3=Solar */ + public @Nullable HeatMode poolHeatMode; + /** spa temperature set point */ + public int spaSetPoint; + /** spa heat mode - 0=Off, 1=Heater, 2=Solar Pref, 3=Solar */ + public @Nullable HeatMode spaHeatMode; + + /** + * Constructure to create an empty status packet + */ + public PentairHeatStatus() { + } + + public PentairHeatStatus(PentairPacket p) { + parsePacket(p); + } + + public void parsePacket(PentairPacket 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; + } +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairIntelliChem.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairIntelliChem.java new file mode 100644 index 0000000000000..6a28425c29572 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairIntelliChem.java @@ -0,0 +1,249 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import javax.measure.quantity.Temperature; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.pentair.internal.handler.PentairControllerHandler; +import org.openhab.binding.pentair.internal.handler.PentairIntelliChlorHandler; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.SIUnits; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class for the pentair controller schedules. + * + * @author Jeff James - initial contribution + * + */ +@NonNullByDefault +public class PentairIntelliChem { + private final Logger logger = LoggerFactory.getLogger(PentairIntelliChem.class); + + public static final int PHREADINGHI = 0; + public static final int PHREADINGLO = 1; + public static final int ORPREADINGHI = 2; + public static final int ORPREADINGLO = 3; + public static final int PHSETPOINTHI = 4; + public static final int PHSETPOINTLO = 5; + public static final int ORPSETPOINTHI = 6; + public static final int ORPSETPOINTLO = 7; + public static final int TANK1 = 20; + public static final int TANK2 = 21; + public static final int CALCIUMHARDNESSHI = 23; + public static final int CALCIUMHARDNESSLO = 24; + public static final int CYAREADING = 27; + public static final int TOTALALKALINITYREADING = 28; + public static final int WATERFLOW = 30; + public static final int MODE1 = 34; + public static final int MODE2 = 35; + + public double phReading; + public int orpReading; + public double phSetPoint; + public int orpSetPoint; // Oxidation Reduction Potential + public int tank1; + public int tank2; + public int calciumHardness; + public int cyaReading; // Cyanuric Acid + public int totalAlkalinity; + public boolean waterFlowAlarm; + public int mode1; + public int mode2; + public double saturationIndex; + + 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 t) { + double temperatureFactor = 0; + int temperature = t.intValue(); + + if (t.getUnit() == 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 totalAlkalinity - 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() { + // 12.1 for non-salt; 12.2 for salt + + if (PentairIntelliChlorHandler.onlineChlorinator != null) { + return 12.2; + } + + return 12.1; + } + + public double calcSaturationIndex() { + QuantityType temperature; + double alkalinityFactor; + double temperatureFactor; + double saturationIndex; + + PentairControllerHandler pch = PentairControllerHandler.onlineController; + + if (pch != null) { + temperature = pch.getWaterTemp(); + temperatureFactor = calcTemperatureFactor(temperature); + } else { + temperatureFactor = .4; + } + + alkalinityFactor = calcAlkalinityFactor(); + + saturationIndex = this.phReading + calcCalciumHardnessFactor() + alkalinityFactor + temperatureFactor + - calcTotalDisovledSolidsFactor(); + + return saturationIndex; + } + + public void parsePacket(PentairPacket p) { + if (p.getLength() != 41) { + logger.debug("Intellichem packet not 41 bytes long"); + return; + } + + phReading = (((p.getByte(PHREADINGHI) & 0xFF) * 256) + (p.getByte(PHREADINGLO) & 0xFF)) / 100.0; + orpReading = ((p.getByte(ORPREADINGHI) & 0xFF) * 256) + (p.getByte(ORPREADINGLO) & 0xFF); + phSetPoint = (((p.getByte(PHSETPOINTHI) & 0xFF) * 256) + (p.getByte(PHSETPOINTLO) & 0xFF)) / 100.0; + orpSetPoint = ((p.getByte(ORPSETPOINTHI) & 0xFF) * 256) + (p.getByte(ORPSETPOINTLO) & 0xFF); + tank1 = p.getByte(TANK1); + tank2 = p.getByte(TANK2); + calciumHardness = ((p.getByte(CALCIUMHARDNESSHI) & 0xFF) * 256) + (p.getByte(CALCIUMHARDNESSLO) & 0xFF); + cyaReading = p.getByte(CYAREADING); + totalAlkalinity = (p.getByte(TOTALALKALINITYREADING) & 0xFF); + waterFlowAlarm = p.getByte(WATERFLOW) != 0x00; + mode1 = p.getByte(MODE1); + mode2 = p.getByte(MODE2); + + saturationIndex = calcSaturationIndex(); + } + + @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, total alkalinity: %d, water flow alarm: %b, mode1: %h, mode2: %h, saturationindex: %f.1", + phReading, orpReading, phSetPoint, orpSetPoint, tank1, tank2, calciumHardness, cyaReading, + totalAlkalinity, waterFlowAlarm, mode1, mode2, saturationIndex); + + return str; + } +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairIntelliChlorPacket.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairIntelliChlorPacket.java new file mode 100644 index 0000000000000..150c6294bcc21 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairIntelliChlorPacket.java @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.pentair.internal; + +import java.nio.charset.StandardCharsets; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Generic class for the standard pentair package protocol. Includes helpers to generate checksum and extract key bytes + * from packet. + * + * @author Jeff James - initial contribution + * + */ +@NonNullByDefault +public class PentairIntelliChlorPacket extends PentairPacket { + + protected static final int DEST = 2; + public static final int ACTION = 3; + + // Set Generate % + protected static final int SALTOUTPUT = 4; + + // Response to set Generate % + protected static final int SALINITY = 4; + protected static final int STATUS = 5; + + // Response to get version + protected static final int VERSION = 4; + protected 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); + } + + @Override + public int getAction() { + return buf[ACTION] & 0xFF; + } + + public int getVersion() { + if (this.getAction() != 0x03) { + return -1; + } + + return buf[VERSION] & 0xFF; + } + + public String getName() { + if (this.getAction() != 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() { + if (this.getAction() != 0x11) { + return -1; + } + + return buf[SALTOUTPUT] & 0xFF; + } + + // Salinity and LED status are sent on a packet with action is 0x12. This is sent from the chlorinator. + public int getSalinity() { + if (this.getAction() != 0x12) { + return -1; + } + + return (buf[SALINITY] & 0xFF) * 50; + } + + public boolean getOk() { + if (this.getAction() != 0x12) { + return false; + } + + return ((buf[STATUS] & 0xFF) == 0) || ((buf[STATUS] & 0xFF) == 0x80); + } + + public boolean getLowFlow() { + if (this.getAction() != 0x12) { + return false; + } + return (buf[STATUS] & 0x01) != 0; + } + + public boolean getLowSalt() { + if (this.getAction() != 0x12) { + return false; + } + return (buf[STATUS] & 0x02) != 0; + } + + public boolean getVeryLowSalt() { + if (this.getAction() != 0x12) { + return false; + } + return (buf[STATUS] & 0x04) != 0; + } + + public boolean getHighCurrent() { + if (this.getAction() != 0x12) { + return false; + } + return (buf[STATUS] & 0x08) != 0; + } + + public boolean getCleanCell() { + if (this.getAction() != 0x12) { + return false; + } + return (buf[STATUS] & 0x10) != 0; + } + + public boolean getLowVoltage() { + if (this.getAction() != 0x12) { + return false; + } + return (buf[STATUS] & 0x20) != 0; + } + + public boolean getLowWaterTemp() { + if (this.getAction() != 0x12) { + return false; + } + return (buf[STATUS] & 0x40) != 0; + } +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacket.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacket.java index 59b37e7f4ef5f..5dec15e63fb3a 100644 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacket.java +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacket.java @@ -12,6 +12,10 @@ */ package org.openhab.binding.pentair.internal; +import java.nio.ByteBuffer; + +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Generic class for the standard pentair package protocol. Includes helpers to generate checksum and extract key bytes * from packet. @@ -19,6 +23,7 @@ * @author Jeff James - initial contribution * */ +@NonNullByDefault public class PentairPacket { protected static final char[] HEXARRAY = "0123456789ABCDEF".toCharArray(); @@ -49,7 +54,12 @@ public PentairPacket() { * @param buf Array of bytes to be used to populate packet. */ public PentairPacket(byte[] buf) { - this.buf = buf; + this(buf, buf.length); + } + + public PentairPacket(byte[] buf, int l) { + this.buf = new byte[l]; + System.arraycopy(buf, 0, this.buf, 0, l); initialized = true; } @@ -66,13 +76,17 @@ public PentairPacket(PentairPacket p) { initialized = true; } + public int getBufLength() { + return buf.length; + } + /** * Gets length of packet * * @return length of packet */ public int getLength() { - return buf[LENGTH]; + return (buf[LENGTH] & 0xFF); } /** @@ -81,7 +95,7 @@ public int getLength() { * @param length length of packet */ public void setLength(int length) { - if (length > buf[LENGTH]) { + if (length > (buf[LENGTH] & 0xFF)) { buf = new byte[length + 6]; } buf[LENGTH] = (byte) length; @@ -93,7 +107,7 @@ public void setLength(int length) { * @return action byte of packet */ public int getAction() { - return buf[ACTION]; + return (buf[ACTION] & 0xFF); // need to convert to unsigned value } /** @@ -111,7 +125,7 @@ public void setAction(int action) { * @return source byte of packet */ public int getSource() { - return buf[SOURCE]; + return (buf[SOURCE] & 0xFF); } /** @@ -129,7 +143,7 @@ public void setSource(int source) { * @return destination byte of packet */ public int getDest() { - return buf[DEST]; + return (buf[DEST] & 0xFF); } /** @@ -141,6 +155,39 @@ public void setDest(int dest) { buf[DEST] = (byte) dest; } + /** + * Gets the preamble byte of packet + */ + public int getPreambleByte() { + return (buf[1] & 0xFF); + } + + /** + * Gets a particular byte of packet + * + * @param number of byte in packet + */ + public int getByte(int num) { + int num2; + + num2 = STARTOFDATA + num; + if (num2 > buf.length) { + return -1; + } + return (buf[num2] & 0xFF); + } + + public void setByte(int num, byte b) { + int num2; + + num2 = STARTOFDATA + num; + if (num2 > buf.length) { + return; + } + + buf[num2] = b; + } + /** * Helper function to convert byte to hex representation * @@ -160,8 +207,8 @@ public static String byteToHex(int b) { * @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); + public static String toHexString(byte[] bytes) { + return toHexString(bytes, bytes.length); } /** @@ -169,7 +216,7 @@ public static String bytesToHex(byte[] bytes) { * @param len Number of bytes to convert * @return hex string */ - public static String bytesToHex(byte[] bytes, int len) { + 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; @@ -180,6 +227,10 @@ public static String bytesToHex(byte[] bytes, int len) { return new String(hexChars); } + public static String toHexString(ByteBuffer buf) { + return toHexString(buf.array(), buf.limit()); + } + /* * (non-Javadoc) * @@ -187,17 +238,7 @@ public static String bytesToHex(byte[] bytes, int len) { */ @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]; + return toHexString(buf, getBufLength()); } /** @@ -214,4 +255,23 @@ public int calcChecksum() { return checksum; } + + public byte[] getFullWriteStream() { + 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; + } } diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketHeatSetPoint.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketHeatSetPoint.java deleted file mode 100644 index ec677046e2f58..0000000000000 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketHeatSetPoint.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) 2010-2020 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.pentair.internal; - -/** - * Pentair heat set point packet specialization of a PentairPacket. Includes public variables for many of the reverse - * engineered - * packet content. - * - * @author Jeff James - initial contribution - * - */ -public class PentairPacketHeatSetPoint extends PentairPacket { - - protected static final int POOLTEMP = 5 + OFFSET; - protected static final int AIRTEMP = 6 + OFFSET; - protected static final int POOLSETPOINT = 7 + OFFSET; - protected static final int SPASETPOINT = 8 + OFFSET; - protected static final int HEATMODE = 9 + OFFSET; - protected static final int SOLARTEMP = 12 + OFFSET; - - protected final String[] heatmodestrs = { "Off", "Heater", "Solar Pref", "Solar" }; - - /** pool temperature set point */ - public int poolsetpoint; - /** pool heat mode - 0=Off, 1=Heater, 2=Solar Pref, 3=Solar */ - public int poolheatmode; - /** pool heat mode as a string */ - public String poolheatmodestr; - /** spa temperature set point */ - public int spasetpoint; - /** spa heat mode - 0=Off, 1=Heater, 2=Solar Pref, 3=Solar */ - public int spaheatmode; - /** spa heat mode as a string */ - public String spaheatmodestr; - - /** - * Constructor to create a specialized packet representing the generic packet. Note, the internal buffer array is - * not - * duplicated. Fills in public class members appropriate with the correct values. - * - * @param p Generic PentairPacket to create specific Status packet - */ - public PentairPacketHeatSetPoint(PentairPacket p) { - super(p); - - poolsetpoint = p.buf[POOLSETPOINT]; - poolheatmode = p.buf[HEATMODE] & 0x03; - poolheatmodestr = heatmodestrs[poolheatmode]; - - spasetpoint = p.buf[SPASETPOINT]; - spaheatmode = (p.buf[HEATMODE] >> 2) & 0x03; - spaheatmodestr = heatmodestrs[spaheatmode]; - } - - /** - * Constructure to create an empty status packet - */ - public PentairPacketHeatSetPoint() { - super(); - } -} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketIntellichlor.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketIntellichlor.java deleted file mode 100644 index 35b33f89d71b5..0000000000000 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketIntellichlor.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright (c) 2010-2020 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.pentair.internal; - -/** - * Pentair Intellichlor specialation of a PentairPacket. Includes public variables for many of the reverse engineered - * packet content. Note, Intellichlor packet is of a different format and all helper functions in the base PentairPacket - * may not apply. - * - * This packet can be a 3 or 4 data byte packet. - * - * 10 02 50 00 00 62 10 03 - * 10 02 00 01 00 00 13 10 03 - * - * @author Jeff James - initial contribution - * - */ -public class PentairPacketIntellichlor extends PentairPacket { // 29 byte packet format - protected static final int CMD = 3; // not sure what this is, needs to be 11 for SALT_OUTPUT or SALINITY to be valid - - // 3 Length command - protected static final int SALTOUTPUT = 4; - - // 4 Length command - protected static final int SALINITY = 4; - - /** length of the packet - 3 or 4 data bytes */ - protected int length; - /** for a saltoutput packet, represents the salt output percent */ - public int saltoutput; - /** for a salinity packet, is value of salinity. Must be multiplied by 50 to get the actual salinity value. */ - public int salinity; - - /** - * Constructor for Intellichlor packet. Does not call super constructure since the Intellichlor packet is structure - * so differently - * - * @param buf - * @param length - */ - public PentairPacketIntellichlor(byte[] buf, int length) { - this.buf = buf; - this.length = length; - - if (length == 3) { - saltoutput = buf[SALTOUTPUT]; - } else if (length == 4) { - salinity = buf[SALINITY] & 0xFF; // make sure it is positive - } - } - - /** - * Constructor for empty Intellichlor packet - */ - public PentairPacketIntellichlor() { - super(); - } - - /* - * (non-Javadoc) - * - * @see org.openhab.binding.pentair.PentairPacket#getLength() - */ - @Override - public int getLength() { - return length; - } - - /* - * (non-Javadoc) - * - * @see org.openhab.binding.pentair.PentairPacket#setLength(int) - */ - @Override - public void setLength(int length) { - if (length != this.length) { - buf = new byte[length + 2]; - } - this.length = length; - } - - /** - * Gets the command byte for this packet - * - * @return command - */ - public int getCmd() { - return buf[CMD]; - } - - @Override - public String toString() { - return bytesToHex(buf, length + 5); - } -} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketPumpStatus.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketPumpStatus.java deleted file mode 100644 index e8816c673bcc4..0000000000000 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketPumpStatus.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) 2010-2020 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.pentair.internal; - -/** - * Pentair pump status packet specialation of a PentairPacket. Includes public variables for many of the reverse - * engineered packet content. - * - * @author Jeff James - initial contribution - * - */ -public class PentairPacketPumpStatus extends PentairPacket { // 15 byte packet format - - protected static final int RUN = 4 + OFFSET; - protected static final int MODE = 5 + OFFSET; // Mode in pump status. Means something else in pump write/response? - protected static final int DRIVESTATE = 6 + OFFSET; // ?? Drivestate in pump status. Means something else in pump - // write/response - protected static final int WATTSH = 7 + OFFSET; - protected static final int WATTSL = 8 + OFFSET; - protected static final int RPMH = 9 + OFFSET; - protected static final int RPML = 10 + OFFSET; - protected static final int PPC = 11 + OFFSET; // ?? - protected static final int ERR = 13 + OFFSET; - protected static final int TIMER = 14 + OFFSET; // ?? Have to explore - protected static final int HOUR = 17 + OFFSET; - protected static final int MIN = 18 + OFFSET; - - /** pump is running */ - public boolean run; - - /** pump mode (1-4) */ - public int mode; - - /** pump drivestate - not sure what this specifically represents. */ - public int drivestate; - /** pump power - in KW */ - public int power; - /** pump rpm */ - public int rpm; - /** pump ppc? */ - public int ppc; - /** byte in packet indicating an error condition */ - public int error; - /** current timer for pump */ - public int timer; - /** hour or packet (based on Intelliflo time setting) */ - public int hour; - /** minute of packet (based on Intelliflo time setting) */ - public int min; - - /** - * Constructor to create a specialized packet representing the generic packet. Note, the internal buffer array is - * not - * duplicated. Fills in public class members appropriate with the correct values. - * - * @param p Generic PentairPacket to create specific Status packet - */ - public PentairPacketPumpStatus(PentairPacket p) { - super(p); - - run = (buf[RUN] == (byte) 0x0A); - mode = buf[MODE]; - drivestate = buf[DRIVESTATE]; - power = (buf[WATTSH] << 8) + buf[WATTSL]; - rpm = (buf[RPMH] << 8) + buf[RPML]; - ppc = buf[PPC]; - error = buf[ERR]; - timer = buf[TIMER]; - hour = buf[HOUR]; - min = buf[MIN]; - } - - /** - * Constructure to create an empty status packet - */ - public PentairPacketPumpStatus() { - super(); - } -} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketStatus.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketStatus.java deleted file mode 100644 index 2ba1240f620c9..0000000000000 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPacketStatus.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright (c) 2010-2020 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.pentair.internal; - -/** - * Pentair status packet specialation of a PentairPacket. Includes public variables for many of the reverse engineered - * packet content. - * - * @author Jeff James - initial contribution - * - */ -public class PentairPacketStatus extends PentairPacket { // 29 byte packet format - - protected static final int HOUR = 4 + OFFSET; - protected static final int MIN = 5 + OFFSET; - protected static final int EQUIP1 = 6 + OFFSET; - protected static final int EQUIP2 = 7 + OFFSET; - protected static final int EQUIP3 = 8 + OFFSET; - protected static final int UOM = 13 + OFFSET; // Celsius (0x04) or Farenheit - protected static final int VALVES = 14 + OFFSET; // Not sure what this actually is? Doesn't seem to be valves - protected static final int UNKNOWN = 17 + OFFSET; // Something to do with heat? - protected static final int POOL_TEMP = 18 + OFFSET; - protected static final int SPA_TEMP = 19 + OFFSET; - protected static final int HEATACTIVE = 20 + OFFSET; // Does not seem to toggle for my system - protected static final int AIR_TEMP = 22 + OFFSET; - protected static final int SOLAR_TEMP = 23 + OFFSET; - protected static final int HEATMODE = 26 + OFFSET; - - /** hour byte of packet */ - public int hour; - /** minute byte of packet */ - public int min; - - /** Individual boolean values representing whether a particular ciruit is on or off */ - public boolean pool, spa, aux1, aux2, aux3, aux4, aux5, aux6, aux7; - public boolean feature1, feature2, feature3, feature4, feature5, feature6, feature7, feature8; - - /** Unit of Measure - Celsius = true, Farenheit = false */ - public boolean uom; - - /** pool temperature */ - public int pooltemp; - /** spa temperature */ - public int spatemp; - /** air temperature */ - public int airtemp; - /** solar temperature */ - public int solartemp; - - /** spa heat mode - 0 = Off, 1 = Heater, 2 = Solar Pref, 3 = Solar */ - public int spaheatmode; - /** pool heat mode - 0 = Off, 1 = Heater, 2 = Solar Pref, 3 = Solar */ - public int poolheatmode; - /** Heat is currently active - note this does not work for my system, but has been documented on the internet */ - public int heatactive; - - /** used to store packet value for reverse engineering, not used in normal operation */ - public int diag; - - /** - * Constructor to create a specialized packet representing the generic packet. Note, the internal buffer array is - * not - * duplicated. Fills in public class members appropriate with the correct values. - * - * @param p Generic PentairPacket to create specific Status packet - */ - public PentairPacketStatus(PentairPacket p) { - super(p); - - hour = buf[HOUR]; - min = buf[MIN]; - pool = (buf[EQUIP1] & 0x20) != 0; - spa = (buf[EQUIP1] & 0x01) != 0; - aux1 = (buf[EQUIP1] & 0x02) != 0; - aux2 = (buf[EQUIP1] & 0x04) != 0; - aux3 = (buf[EQUIP1] & 0x08) != 0; - aux4 = (buf[EQUIP1] & 0x10) != 0; - aux5 = (buf[EQUIP1] & 0x40) != 0; - aux6 = (buf[EQUIP1] & 0x80) != 0; - aux7 = (buf[EQUIP2] & 0x01) != 0; - - feature1 = (buf[EQUIP2] & 0x04) != 0; - feature2 = (buf[EQUIP2] & 0x08) != 0; - feature3 = (buf[EQUIP2] & 0x10) != 0; - feature4 = (buf[EQUIP2] & 0x20) != 0; - feature5 = (buf[EQUIP2] & 0x40) != 0; - feature6 = (buf[EQUIP2] & 0x80) != 0; - feature7 = (buf[EQUIP3] & 0x01) != 0; - feature8 = (buf[EQUIP3] & 0x02) != 0; - - uom = (buf[UOM] & 0x04) != 0; - - diag = buf[HEATACTIVE]; - - pooltemp = buf[POOL_TEMP]; - spatemp = buf[SPA_TEMP]; - airtemp = buf[AIR_TEMP]; - solartemp = buf[SOLAR_TEMP]; - - spaheatmode = (buf[HEATMODE] >> 2) & 0x03; - poolheatmode = buf[HEATMODE] & 0x03; - heatactive = buf[HEATACTIVE]; - } - - /** - * Constructure to create an empty status packet - */ - public PentairPacketStatus() { - super(); - } -} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairParser.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairParser.java new file mode 100644 index 0000000000000..a0393c7a591cc --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairParser.java @@ -0,0 +1,239 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 +*/ +package org.openhab.binding.pentair.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Objects; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 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 MAXPENTAIRPACKET = 50; + + private final Logger logger = LoggerFactory.getLogger(PentairParser.class); + + private enum ParserState { + WAIT_STARTOFPACKET, + CMD_PENTAIR, + CMD_INTELLICHLOR + }; + + @Nullable + private InputStream reader; + + public void setInputStream(InputStream reader) { + this.reader = reader; + } + + /** + * Callback interface when a packet is received + */ + public interface CallbackPentairParser { + public void onPentairPacket(PentairPacket p); + + public void onIntelliChlorPacket(PentairIntelliChlorPacket p); + }; + + private Optional callback = Optional.empty(); + + public void setCallback(CallbackPentairParser cb) { + callback = Optional.of(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() { + logger.trace("parser thread started"); + ByteBuffer buf = ByteBuffer.allocate(MAXPENTAIRPACKET + 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 + + logger.debug("Receieved Pentair Command"); + + 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 > MAXPENTAIRPACKET) { + logger.trace("Received packet longer than {} bytes: {}", MAXPENTAIRPACKET, 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, + PentairPacket.toHexString(buf)); + break; + } + + PentairPacket p = new PentairPacket(buf.array(), buf.limit()); + + logger.debug("PentairPacket: {}", p.toString()); + callback.get().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); + if (length == -1) { + logger.debug("IntelliChlor Packet unseen: command - {}", 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: {}", PentairPacket.toHexString(buf)); + break; // invalid command + } + + buf.flip(); + checksumCalc = calcChecksum(buf.duplicate()); + if ((byte) checksumCalc != (byte) checksumInPacket) { + logger.trace("Invalid Intellichlor checksum: {}", PentairPacket.toHexString(buf)); + break; + } + + PentairIntelliChlorPacket pic = new PentairIntelliChlorPacket(buf.array(), buf.limit()); + + logger.debug("IntelliChlor Packet: {}", pic.toString()); + callback.get().onIntelliChlorPacket(pic); + + break; + } + } catch (IOException e) { + logger.debug("I/O error while reading from stream: {}", e.getMessage()); + Thread.currentThread().interrupt(); + break; // exit while loop + // PentairBaseBridgeHandler will monitor this thread and restart if it exits unexpectedly + } + } + + logger.debug("msg reader thread exited"); + } +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPumpStatus.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPumpStatus.java new file mode 100644 index 0000000000000..b9c78bf0d0d41 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/PentairPumpStatus.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Pentair pump status packet specialation of a PentairPacket. Includes public variables for many of the reverse + * engineered packet content. + * + * @author Jeff James - initial contribution + * + */ +@NonNullByDefault +public class PentairPumpStatus { // 15 byte packet format + private final Logger logger = LoggerFactory.getLogger(PentairPumpStatus.class); + + protected static final int RUN = 0; + protected static final int MODE = 1; // Mode in pump status. Means something else in pump + // write/response? + protected static final int DRIVESTATE = 2; // ?? Drivestate in pump status. Means something else in + // pump write/respoonse + protected static final int WATTSH = 3; + protected static final int WATTSL = 4; + protected static final int RPMH = 5; + protected static final int RPML = 6; + protected static final int GPM = 7; + protected static final int PPC = 8; // not sure what this is? always 0 + protected static final int B09 = 9; + protected static final int STATUS1 = 11; + protected static final int STATUS2 = 12; + protected static final int HOUR = 13; + protected static final int MIN = 14; + + /** 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(PentairPacket p) { + if (p.getLength() != 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; + } +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairControllerHandlerConfig.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairControllerHandlerConfig.java new file mode 100644 index 0000000000000..d7e4711367646 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairControllerHandlerConfig.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Configuration parameters for PentairControllerHandler + * + * @author Jeff James - initial contribution + * + */ +@NonNullByDefault +public class PentairControllerHandlerConfig { + /** ID of thing on the Pentair RS485 bus. */ + public int id = 0; + public boolean synctime = true; +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairIPBridgeConfig.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairIPBridgeConfig.java index c2fce9790c075..e932bbfb9ebef 100644 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairIPBridgeConfig.java +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairIPBridgeConfig.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.pentair.internal.config; -import org.apache.commons.lang.builder.ToStringBuilder; +import org.eclipse.jdt.annotation.NonNullByDefault; /** * Configuration parameters for IP Bridge @@ -20,17 +20,14 @@ * @author Jeff James - initial contribution * */ +@NonNullByDefault public class PentairIPBridgeConfig { /** IP address of destination */ - public String address; + public String address = ""; /** Port of destination */ - public Integer port; - + public int port = 10000; /** ID to use when sending commands on the Pentair RS485 bus. */ - public Integer id; - - @Override - public String toString() { - return new ToStringBuilder(this).append("address", address).append("port", port).append("id", id).toString(); - } + public int id = 34; + /** enable automatic discovery */ + public boolean discovery = false; } diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairIntelliChemHandlerConfig.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairIntelliChemHandlerConfig.java new file mode 100644 index 0000000000000..21ac3ff423297 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairIntelliChemHandlerConfig.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Configuration parameters for PentairIntelliChemHandler + * + * @author Jeff James - initial contribution + * + */ +@NonNullByDefault +public class PentairIntelliChemHandlerConfig { + /** ID of thing on the Pentair RS485 bus. */ + public int id = 0; +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairIntelliFloHandlerConfig.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairIntelliFloHandlerConfig.java new file mode 100644 index 0000000000000..1992e8f0aa088 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairIntelliFloHandlerConfig.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Configuration parameters for PentairIntelliFloHandler + * + * @author Jeff James - initial contribution + * + */ +@NonNullByDefault +public class PentairIntelliFloHandlerConfig { + /** ID of thing on the Pentair RS485 bus. */ + public int id = 0; +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairSerialBridgeConfig.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairSerialBridgeConfig.java index e4731a10fb886..00ac7692f5c9a 100644 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairSerialBridgeConfig.java +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/config/PentairSerialBridgeConfig.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.pentair.internal.config; -import org.apache.commons.lang.builder.ToStringBuilder; +import org.eclipse.jdt.annotation.NonNullByDefault; /** * Configuration parameters for Serial Bridge @@ -20,14 +20,12 @@ * @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; + public String serialPort = ""; /** ID to use when sending commands on the Pentair RS485 bus. */ - public Integer id; - - @Override - public String toString() { - return new ToStringBuilder(this).append("serialPort", serialPort).append("id", id).toString(); - } + public int id = 34; + /** Enable automotic discovery */ + public boolean discovery = false; } diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairBaseBridgeHandler.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairBaseBridgeHandler.java index 9f53651c13e28..faa04fdaf3c19 100644 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairBaseBridgeHandler.java +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairBaseBridgeHandler.java @@ -12,24 +12,40 @@ */ package org.openhab.binding.pentair.internal.handler; -import static org.openhab.binding.pentair.internal.PentairBindingConstants.INTELLIFLO_THING_TYPE; - import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; -import java.util.ArrayList; +import java.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.Optional; +import java.util.Set; import java.util.concurrent.ScheduledFuture; 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.PentairDiscoveryService; +import org.openhab.binding.pentair.internal.PentairIntelliChlorPacket; import org.openhab.binding.pentair.internal.PentairPacket; -import org.openhab.binding.pentair.internal.PentairPacketIntellichlor; +import org.openhab.binding.pentair.internal.PentairParser; +import org.openhab.binding.pentair.internal.PentairParser.CallbackPentairParser; 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; @@ -46,23 +62,42 @@ * @author Jeff James - Initial contribution * */ -public abstract class PentairBaseBridgeHandler extends BaseBridgeHandler { +@NonNullByDefault +public abstract class PentairBaseBridgeHandler extends BaseBridgeHandler implements CallbackPentairParser { 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; + protected Optional reader = Optional.empty(); + /** output stream - subclass needs to assign in connect function */ + protected Optional writer = Optional.empty(); + + protected final PentairParser parser = new PentairParser(); + /** 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; + private @Nullable Thread parserThread; + + /** job for monitoring IO */ + protected @Nullable ScheduledFuture monitorIOJob; /** 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 unregistered = new ArrayList<>(); + /** array to keep track of IDs seen on the Pentair bus that are not configured yet */ + protected final Set unregistered = new HashSet(); + + protected final Map equipment = new HashMap<>(); + + protected ConnectState connectstate = ConnectState.INIT; + + private final ReentrantLock lock = new ReentrantLock(); + private Condition waitAck = lock.newCondition(); + private int ackResponse = -1; + + protected boolean discovery = false; + + protected @Nullable PentairDiscoveryService discoveryService; + + public void setDiscoveryService(PentairDiscoveryService discoveryService) { + this.discoveryService = discoveryService; + } /** * Gets pentair bus id @@ -73,11 +108,13 @@ public int getId() { return id; } - private enum ParserState { - WAIT_SOC, - CMD_PENTAIR, - CMD_INTELLICHLOR - } + protected enum ConnectState { + CONNECTING, + DISCONNECTED, + CONNECTED, + INIT, + CONFIGERROR + }; /** * Constructor @@ -86,6 +123,13 @@ private enum ParserState { */ PentairBaseBridgeHandler(Bridge bridge) { super(bridge); + parser.setCallback(this); + connectstate = ConnectState.INIT; + } + + @Override + public Collection> getServices() { + return Collections.singleton(PentairDiscoveryService.class); } @Override @@ -99,327 +143,247 @@ public void handleCommand(ChannelUID channelUID, Command command) { public void initialize() { logger.debug("initializing Pentair Bridge handler."); - connect(); - - pollingjob = scheduler.scheduleWithFixedDelay(new PumpStatus(), 10, 120, TimeUnit.SECONDS); + internalConnect(); } @Override public void dispose() { - logger.debug("Handler disposed."); - pollingjob.cancel(true); - disconnect(); + if (monitorIOJob != null) { + monitorIOJob.cancel(true); + } + + internalDisconnect(); } - /** - * Abstract method for creating connection. Must be implemented in subclass. + /* + * Custom function to call during initialization to notify the bridge. childHandlerInitialized is not called + * until the child thing actually goes to the ONLINE status. */ - protected abstract void connect(); + public void childHandlerInitializing(ThingHandler childHandler, Thing childThing) { + if (childHandler instanceof PentairBaseThingHandler) { + equipment.put(((PentairBaseThingHandler) childHandler).id, (PentairBaseThingHandler) childHandler); + unregistered.remove(((PentairBaseThingHandler) childHandler).id); + } + } - /** - * Abstract method for disconnect. Must be implemented in subclass - */ - protected abstract void disconnect(); + @Override + public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) { + if (childHandler instanceof PentairBaseThingHandler) { + equipment.remove(((PentairBaseThingHandler) childHandler).id); + } + } /** - * Helper function to find a Thing assigned to this bridge with a specific pentair bus id. + * Abstract method for creating connection. Must be implemented in subclass. + * Return 0 if all goes well. Must call setInputStream and setOutputStream before exciting. * - * @param id Pentiar bus id - * @return Thing object. null if id is not found. + * @throws Exception */ - public Thing findThing(int id) { - List things = getThing().getThings(); + protected abstract int connect(); - for (Thing t : things) { - PentairBaseThingHandler handler = (PentairBaseThingHandler) t.getHandler(); + protected abstract void disconnect(); - if (handler != null && handler.getPentairID() == id) { - return t; - } + private void internalConnect() { + if (connectstate != ConnectState.DISCONNECTED && connectstate != ConnectState.INIT) { + logger.debug("_connect() without ConnectState == DISCONNECTED or INIT: {}", connectstate); } - return null; - } + connectstate = ConnectState.CONNECTING; - /** - * 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; - } + if (connect() != 0) { + connectstate = ConnectState.CONFIGERROR; - /** - * Gets a single byte from reader input stream - * - * @param s used during debug to identify proper state transitioning - * @return next byte from reader - * @throws EOBException - * @throws IOException - */ - private int getByte(ParserState s) throws EOBException, IOException { - int c = 0; - - c = reader.read(); - if (c == -1) { - // EOBException is thrown if no more bytes in buffer. This exception is used to exit the parser when full - // packet is not in buffer - throw new EOBException(); + return; + } + + // montiorIOJob will only start after a successful connection + if (monitorIOJob == null) { + monitorIOJob = scheduler.scheduleWithFixedDelay(() -> monitorIO(), 60, 30, TimeUnit.SECONDS); } - return c; + parserThread = new Thread(parser, "OH-pentair-" + this.getThing().getUID() + "-parser"); + parserThread.setDaemon(true); + parserThread.start(); + + if (reader.isPresent() && writer.isPresent()) { + updateStatus(ThingStatus.ONLINE); + connectstate = ConnectState.CONNECTED; + } else { + // this should never occur + logger.debug("Reader or Write did not get created during connect()"); + } } - /** - * 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(); + public void setInputStream(InputStream inputStream) { + reader.ifPresent(close -> { + try { + close.close(); + } catch (IOException e) { + logger.debug("setInputStream: Exception error while closing: {}", e.getMessage()); + } + }); + + reader = Optional.of(new BufferedInputStream(inputStream)); + parser.setInputStream(inputStream); + } + + public void setOutputStream(OutputStream outputStream) { + writer.ifPresent(close -> { + try { + close.close(); + } catch (IOException e) { + logger.debug("setOutputStream: Exception error while closing: {}", e.getMessage()); } + }); - buf[start + i] = (byte) c; + writer = Optional.of(new BufferedOutputStream(outputStream)); + } + + private void internalDisconnect() { + if (parserThread != null) { + try { + parserThread.interrupt(); + parserThread.join(3000); // wait for thread to complete + } catch (InterruptedException e) { + // do nothing + } + parserThread = null; } - return i; + reader.ifPresent(close -> { + try { + close.close(); + } catch (IOException e) { + logger.debug("setInputStream: Exception error while closing: {}", e.getMessage()); + } + }); + + writer.ifPresent(close -> { + try { + close.close(); + } catch (IOException e) { + logger.debug("setOutputStream: Exception error while closing: {}", e.getMessage()); + } + }); + + disconnect(); + + connectstate = ConnectState.DISCONNECTED; + } + + // method to poll to try and reconnect upon being disconnected. Note this should only be started on an initial + private void monitorIO() { + logger.debug("MonitorIO"); + // ConnectState.DISCONNECTED implies the connection had at one time been successfully connected and + // therefore the + // configuration is correct. This state can be reached when an exception happens where the connection has + // been + // interrupted. + switch (connectstate) { + case DISCONNECTED: // Try to reconnect + case CONFIGERROR: + internalConnect(); + break; + case CONNECTED: + // Check if parser thread has terminated and if it has reconnect. This will take down the interface and + // restart the interface. + Objects.requireNonNull(parserThread); + if (!parserThread.isAlive()) { + internalDisconnect(); + internalConnect(); + } + break; + case CONNECTING: // in the process of connecting or initing, do nothing + case INIT: + break; + } } /** - * 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 + * Helper function to find a Thing assigned to this bridge with a specific pentair bus id. * + * @param id Pentair bus id + * @return Thing object. null if id is not found. */ - class PumpStatus implements Runnable { - @Override - public void run() { - List things = getThing().getThings(); + public @Nullable Thing findThing(int id) { + List things = getThing().getThings(); - // FF 00 FF A5 00 60 10 07 00 01 1C - byte[] packet = { (byte) 0xA5, (byte) 0x00, (byte) 0x00, (byte) id, (byte) 0x07, (byte) 0x00 }; + for (Thing t : things) { + PentairBaseThingHandler handler = (PentairBaseThingHandler) t.getHandler(); - PentairPacket p = new PentairPacket(packet); + if (handler != null && handler.getPentairID() == id) { + return t; + } + } - for (Thing t : things) { - if (!t.getThingTypeUID().equals(INTELLIFLO_THING_TYPE)) { - continue; - } + return null; + } - p.setDest(((PentairIntelliFloHandler) t.getHandler()).id); - writePacket(p); - try { - Thread.sleep(300); // make sure each pump has time to respond - } catch (InterruptedException e) { - break; + @Override + public void onPentairPacket(PentairPacket p) { + PentairBaseThingHandler thinghandler; + + thinghandler = equipment.get(p.getSource()); + + if (thinghandler == null) { + int source = p.getSource(); + 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 ({}): {}", p.getSource(), p); + } else if (!unregistered.contains(p.getSource())) { // if not yet seen, print out ONE message and discover + if (sourceType == 0x01) { // controller + if (PentairControllerHandler.onlineController == null) { // only register one + // controller + if (discovery && discoveryService != null) { + discoveryService.notifyDiscoveredController(source); + } + } + } else if (sourceType == 0x06) { + if (discovery && discoveryService != null) { + discoveryService.notifyDiscoverdIntelliflo(source); + } + } else if (sourceType == 0x09) { + if (discovery && discoveryService != null) { + discoveryService.notifyDiscoveryIntellichem(source); + } } + + logger.debug("First command from unregistered device ({}): {}", p.getSource(), p); + unregistered.add(p.getSource()); + } else { + logger.trace("Subsequent command from unregistered device ({}): {}", p.getSource(), p); } + } else { + logger.debug("Received pentair command: {}", p); + + thinghandler.processPacketFrom(p); + ackResponse(p.getAction()); } } - /** - * Implements the thread to read and parse the input stream. Once a packet can be indentified, it locates the - * representive sending Thing and dispositions the packet so it can be further processed. - * - * @author Jeff James - initial implementation - * - */ - class Parser implements Runnable { - @Override - public void run() { - logger.debug("parser thread started"); - byte buf[] = new byte[40]; - int c; - int chksum, i, length; - Thing thing; - PentairBaseThingHandler thinghandler; - - ParserState parserstate = ParserState.WAIT_SOC; + @Override + public void onIntelliChlorPacket(PentairIntelliChlorPacket p) { + PentairBaseThingHandler thinghandler; - try { - while (!Thread.currentThread().isInterrupted()) { - c = getByte(parserstate); - - switch (parserstate) { - case WAIT_SOC: - if (c == 0xFF) { // for CMD_PENTAIR, we need at lease one 0xFF - do { - c = getByte(parserstate); - } while (c == 0xFF); // consume all 0xFF - - if (c == 0x00) { - parserstate = ParserState.CMD_PENTAIR; - } - } - - if (c == 0x10) { - parserstate = ParserState.CMD_INTELLICHLOR; - } - break; - case CMD_PENTAIR: - parserstate = ParserState.WAIT_SOC; // any break will go back to WAIT_SOC - - if (c != 0xFF) { - logger.debug("FF00 !FF"); - break; - } - - if (getBytes(buf, 0, 6) != 6) { // read enough to get the length - logger.debug("Unable to read 6 bytes"); - - break; - } - if (buf[0] != (byte) 0xA5) { - logger.debug("FF00FF !A5"); - break; - } - - length = buf[5]; - if (length == 0) { - logger.debug("Command length of 0"); - } - if (length > 34) { - logger.debug("Received packet longer than 34 bytes: {}", length); - break; - } - if (getBytes(buf, 6, length) != length) { // read remaining packet - break; - } - - chksum = 0; - for (i = 0; i < length + 6; i++) { - chksum += buf[i] & 0xFF; - } - - c = getByte(parserstate) << 8; - c += getByte(parserstate); - - if (c != chksum) { - logger.debug("Checksum error: {}", PentairPacket.bytesToHex(buf, length + 6)); - break; - } - - PentairPacket p = new PentairPacket(buf); - - thing = findThing(p.getSource()); - if (thing == null) { - if ((p.getSource() >> 8) == 0x02) { // control panels are 0x3*, don't treat as an - // unregistered device - logger.trace("Command from control panel device ({}): {}", p.getSource(), p); - } else if (!unregistered.contains(p.getSource())) { // if not yet seen, print out log - // message once - logger.info("Command from unregistered device ({}): {}", p.getSource(), p); - unregistered.add(p.getSource()); - } else { - logger.trace("Command from unregistered device ({}): {}", p.getSource(), p); - } - break; - } - - thinghandler = (PentairBaseThingHandler) thing.getHandler(); - if (thinghandler == null) { - logger.debug("Thing handler = null"); - break; - } - - logger.trace("Received pentair command: {}", p); - - thinghandler.processPacketFrom(p); - - break; - case CMD_INTELLICHLOR: - parserstate = ParserState.WAIT_SOC; - - buf[0] = 0x10; // 0x10 is included in checksum - if (c != (byte) 0x02) { - break; - } - - buf[1] = 0x2; - length = 3; - // assume 3 byte command, plus 1 checksum, plus 0x10, 0x03 - if (getBytes(buf, 2, 6) != 6) { - break; - } - - // Check to see if this is a 3 or 4 byte command - if ((buf[6] != (byte) 0x10 || buf[7] != (byte) 0x03)) { - length = 4; - - buf[8] = (byte) getByte(parserstate); - if ((buf[7] != (byte) 0x10) && (buf[8] != (byte) 0x03)) { - logger.debug("Invalid Intellichlor command: {}", - PentairPacket.bytesToHex(buf, length + 6)); - break; // invalid command - } - } - - chksum = 0; - for (i = 0; i < length + 2; i++) { - chksum += buf[i] & 0xFF; - } - - c = buf[length + 2] & 0xFF; - if (c != (chksum & 0xFF)) { // make sure it matches chksum - logger.debug("Invalid Intellichlor checksum: {}", - PentairPacket.bytesToHex(buf, length + 6)); - break; - } - - PentairPacketIntellichlor pic = new PentairPacketIntellichlor(buf, length); - - thing = findThing(0); - - if (thing == null) { - if (!unregistered.contains(0)) { // if not yet seen, print out log message - logger.info("Command from unregistered Intelliflow: {}", pic); - unregistered.add(0); - } else { - logger.trace("Command from unregistered Intelliflow: {}", pic); - } - - break; - } - - thinghandler = (PentairBaseThingHandler) thing.getHandler(); - if (thinghandler == null) { - logger.debug("Thing handler = null"); - break; - } - - thinghandler.processPacketFrom(pic); - - break; - } + thinghandler = equipment.get(0); + + if (thinghandler == null) { + if (!unregistered.contains(0)) { // if not yet seen, print out log message + if (discovery && discoveryService != null) { + discoveryService.notifyDiscoveredIntellichlor(0); } - } 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(" First command from unregistered Intelliflow: {}", p); + unregistered.add(0); + } else { + logger.trace("Subsequent command from unregistered Intelliflow: {}", p); } - logger.debug("msg reader thread exited"); + return; } + + thinghandler.processPacketFrom(p); } /** @@ -428,26 +392,74 @@ public void run() { * @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 + writePacket(p, -1, 0); + } + public boolean writePacket(PentairPacket p, int response, int retries) { + boolean bReturn = true; + + try { + byte[] buf; + int nRetries = retries; + + if (!writer.isPresent()) { + logger.debug("writePacket: writer = null"); + return false; + } p.setSource(id); - System.arraycopy(preamble, 0, buf, 0, 5); - System.arraycopy(p.buf, 0, buf, 5, p.getLength() + 6); - int checksum = p.calcChecksum(); + buf = p.getFullWriteStream(); + + lock.lock(); + ackResponse = response; - buf[p.getLength() + 11] = (byte) ((checksum >> 8) & 0xFF); - buf[p.getLength() + 12] = (byte) (checksum & 0xFF); + do { + logger.debug("Writing packet: {}", PentairPacket.toHexString(buf)); - logger.debug("Writing packet: {}", PentairPacket.bytesToHex(buf)); + writer.get().write(buf, 0, buf.length); + writer.get().flush(); - writer.write(buf, 0, 5 + p.getLength() + 8); - writer.flush(); + if (response != -1) { + logger.debug("writePacket: wait for ack (response: {}, retries: {})", response, nRetries); + bReturn = waitAck.await(1000, TimeUnit.MILLISECONDS); // bReturn will be false if timeout + nRetries--; + } + } while (!bReturn && (nRetries >= 0)); } catch (IOException e) { - logger.trace("I/O error while writing stream", e); + logger.debug("I/O error while writing stream: {}", e.getMessage()); + internalDisconnect(); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } catch (InterruptedException e) { + logger.debug("writePacket: InterruptedException: {}", e.getMessage()); + Thread.currentThread().interrupt(); + } finally { + lock.unlock(); + } + + if (!bReturn) { + logger.debug("writePacket: timeout"); + } + return bReturn; + } + + /** + * 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(); + try { + waitAck.signalAll(); + } catch (IllegalMonitorStateException e) { + logger.debug("ackResponse: illegal monitor exception: {}", e.getMessage()); + } finally { + lock.unlock(); } } } diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairBaseThingHandler.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairBaseThingHandler.java index 81f3fd0899e33..01816d5ce31f7 100644 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairBaseThingHandler.java +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairBaseThingHandler.java @@ -12,9 +12,22 @@ */ package org.openhab.binding.pentair.internal.handler; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.pentair.internal.PentairPacket; +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.library.unit.Units; +import org.openhab.core.thing.Bridge; 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.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Abstract class for all Pentair Things. @@ -22,14 +35,70 @@ * @author Jeff James - Initial contribution * */ +@NonNullByDefault public abstract class PentairBaseThingHandler extends BaseThingHandler { + private final Logger logger = LoggerFactory.getLogger(PentairBaseThingHandler.class); + /** ID of Thing on Pentair bus */ protected int id; + protected boolean waitStatusForOnline = false; public PentairBaseThingHandler(Thing thing) { super(thing); } + // this method will be overridden by + public abstract void readConfiguration(); + + @Override + public void initialize() { + readConfiguration(); + + PentairBaseBridgeHandler bh = getBridgeHandler(); + + if (bh == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Device cannot be created without a bridge."); + return; + } + + if (bh.equipment.get(id) != null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "A device with this ID has already been initialized."); + return; + } + + bh.childHandlerInitializing(this, this.getThing()); + + waitStatusForOnline = false; + 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(); + } + } + /** * Gets Pentair bus ID of Thing * @@ -39,8 +108,69 @@ public int getPentairID() { return id; } + public void writePacket(byte[] packet) { + writePacket(packet, -1, 0); + } + + public boolean writePacket(byte[] packet, int response, int retries) { + PentairPacket p = new PentairPacket(packet); + + return writePacket(p, response, retries); + } + + public boolean writePacket(PentairPacket p, int response, int retries) { + Bridge bridge = this.getBridge(); + if (bridge == null) { + return false; + } + + PentairBaseBridgeHandler bbh = (PentairBaseBridgeHandler) bridge.getHandler(); + if (bbh == null) { + return false; + } + + return bbh.writePacket(p, response, retries); + } + + public @Nullable 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; + } + + /** + * Helper function to update channel. + */ + public void updateChannel(String channel, boolean value) { + updateState(channel, (value) ? OnOffType.ON : OnOffType.OFF); + } + + public void updateChannel(String channel, int value) { + updateState(channel, new DecimalType(value)); + } + + public void updateChannel(String channel, double value) { + updateState(channel, new DecimalType(value)); + } + + public void updateChannel(String channel, String value) { + updateState(channel, new StringType(value)); + } + + public void updateChannelPower(String channel, int value) { + updateState(channel, new QuantityType<>(value, Units.WATT)); + } + /** - * Abstract function to be implemented by Thing to dispose/parse a received packet + * Abstract function to be implemented by Thing to parse a received packet * * @param p */ diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairControllerHandler.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairControllerHandler.java new file mode 100644 index 0000000000000..150416a166b40 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairControllerHandler.java @@ -0,0 +1,1131 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal.handler; + +import static org.openhab.binding.pentair.internal.PentairBindingConstants.*; + +import java.util.Calendar; +import java.util.Map; +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.ExpiringCache; +import org.openhab.binding.pentair.internal.PentairControllerCircuit; +import org.openhab.binding.pentair.internal.PentairControllerConstants; +import org.openhab.binding.pentair.internal.PentairControllerConstants.LightMode; +import org.openhab.binding.pentair.internal.PentairControllerSchedule; +import org.openhab.binding.pentair.internal.PentairControllerStatus; +import org.openhab.binding.pentair.internal.PentairHeatStatus; +import org.openhab.binding.pentair.internal.PentairPacket; +import org.openhab.binding.pentair.internal.config.PentairControllerHandlerConfig; +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.library.unit.ImperialUnits; +import org.openhab.core.library.unit.SIUnits; +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 { + protected static final int NUM_CIRCUITS = PentairControllerStatus.NUMCIRCUITS; + protected static final int NUM_SCHEDULES = 9; + + protected static final String[] CIRCUIT_GROUPS = { CONTROLLER_SPACIRCUIT, CONTROLLER_AUX1CIRCUIT, + CONTROLLER_AUX2CIRCUIT, CONTROLLER_AUX3CIRCUIT, CONTROLLER_AUX4CIRCUIT, CONTROLLER_POOLCIRCUIT, + CONTROLLER_AUX5CIRCUIT, CONTROLLER_AUX6CIRCUIT, CONTROLLER_AUX7CIRCUIT, CONTROLLER_AUX8CIRCUIT, + CONTROLLER_FEATURE1, CONTROLLER_FEATURE2, CONTROLLER_FEATURE3, CONTROLLER_FEATURE4, CONTROLLER_FEATURE5, + CONTROLLER_FEATURE6, CONTROLLER_FEATURE7, CONTROLLER_FEATURE8 }; + + // only one controller can be online at a time, used to validate only one is online & to access status + public static @Nullable PentairControllerHandler onlineController; + public boolean serviceMode = false; + public boolean uom = false; + + public boolean syncTime = true; + + private final Logger logger = LoggerFactory.getLogger(PentairControllerHandler.class); + + protected @Nullable ScheduledFuture syncTimeJob; + + private int preambleByte = -1; // 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 long lastScheduleTypeWrite; + + private static final int CACHE_EXPIRY = (int) TimeUnit.SECONDS.toMillis(10); + private static final int CACHE_EXPIRY_LONG = (int) TimeUnit.MINUTES.toMillis(10); + + protected final ExpiringCache controllerStatusCache = new ExpiringCache<>(CACHE_EXPIRY); + protected final ExpiringCache heatStatusCache = new ExpiringCache<>(CACHE_EXPIRY); + + private int majorrev, minorrev; + + protected final ExpiringCache[] circuitsCache = new ExpiringCache[NUM_CIRCUITS]; + protected final ExpiringCache[] schedulesCache = new ExpiringCache[NUM_SCHEDULES]; + + protected @Nullable LightMode lightMode; + + public PentairControllerHandler(Thing thing) { + super(thing); + + for (int i = 0; i < NUM_SCHEDULES; i++) { + schedulesCache[i] = new ExpiringCache<>(CACHE_EXPIRY_LONG); + } + + for (int i = 0; i < NUM_CIRCUITS; i++) { + circuitsCache[i] = new ExpiringCache<>(CACHE_EXPIRY_LONG); + } + } + + @Override + public void readConfiguration() { + PentairControllerHandlerConfig config = getConfigAs(PentairControllerHandlerConfig.class); + + this.id = config.id; + this.syncTime = config.synctime; + } + + @Override + public void goOnline() { + if (onlineController != null) { + logger.debug("Another controller is already configured"); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Another controller is already configured."); + } else { + super.goOnline(); + } + } + + @Override + public void finishOnline() { + super.finishOnline(); + + // setup syncTimeJob to run once a day, initial time to sync is 3 minutes after controller goes online. This is + // to prevent collision with main thread queries on initial startup + syncTimeJob = scheduler.scheduleWithFixedDelay(() -> syncTime(), 3, 24 * 60 * 60, TimeUnit.MINUTES); + + scheduler.execute(() -> readControllerSettings()); + } + + public void syncTime() { + boolean synctime = ((boolean) getConfig().get("synctime")); + if (synctime) { + logger.debug("Synchronizing System Time with Pentair controller"); + Calendar now = Calendar.getInstance(); + sendClockSettings(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 readControllerSettings() { + int i; + + requestSWVersion(); + requestHeatStatus(); + requestClockSettings(); + + for (i = 1; i <= NUM_CIRCUITS; i++) { + requestCircuitNameFunction(i); + } + + for (i = 1; i <= NUM_SCHEDULES; i++) { + requestSchedule(i); + } + + requestLightGroups(); + requestValves(); + } + + @Override + public void goOffline(ThingStatusDetail detail) { + super.goOffline(detail); + + if (syncTimeJob != null) { + syncTimeJob.cancel(true); + } + + onlineController = null; + } + + public @Nullable PentairControllerCircuit getCircuitByGroupID(String groupID) { + for (ExpiringCache circuitItem : circuitsCache) { + PentairControllerCircuit circuit = circuitItem.getLastKnownValue(); + if (circuit == null) { + break; + } + + if (circuit.getGroupID().equals(groupID)) { + return circuit; + } + } + + return null; + } + + public int getScheduleNumber(String name) { + int i; + + for (i = 1; i <= NUM_SCHEDULES; i++) { + String str = String.format(CONTROLLER_SCHEDULE, i); + if (str.equals(name)) { + return i; + } + } + + return 0; + } + + public Unit getUOM() { + return (this.uom) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT; + } + + public QuantityType getWaterTemp() { + PentairControllerStatus status = this.controllerStatusCache.getLastKnownValue(); + Objects.requireNonNull(status); + return new QuantityType(status.poolTemp, getUOM()); + } + + 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 groupId = channelUID.getGroupId(); + + if (groupId == null) { + return; + } + + if (command instanceof RefreshType) { + logger.debug("handleCommand (refresh): {}", channelUID.getId()); + + switch (channelUID.getIdWithoutGroup()) { + case CONTROLLER_CIRCUITSWITCH: + case CONTROLLER_TEMPERATURE: + case CONTROLLER_AIRTEMPERATURE: + case CONTROLLER_SOLARTEMPERATURE: + case CONTROLLER_UOM: + case CONTROLLER_SERVICEMODE: + case CONTROLLER_SOLARON: + case CONTROLLER_HEATERON: + case CONTROLLER_HEATERDELAY: { + PentairControllerStatus status = controllerStatusCache.getValue(() -> { + requestControllerStatus(); + }); + if (status != null) { + refreshControllerStatusChannels(channelUID, status); + } + break; + } + case CONTROLLER_LIGHTMODE: { + // Unable to find command update the lightMode from the controller, so setting to last set value + if (lightMode != null) { + updateState(channelUID, new StringType(lightMode.name())); + } + break; + } + case CONTROLLER_SETPOINT: + case CONTROLLER_HEATMODE: { + PentairHeatStatus status = heatStatusCache.getValue(() -> { + requestHeatStatus(); + }); + if (status != null) { + refreshHeatStatusChannels(channelUID, status); + } + break; + } + case CONTROLLER_SCHEDULESTRING: + case CONTROLLER_SCHEDULETYPE: + case CONTROLLER_SCHEDULECIRCUIT: + case CONTROLLER_SCHEDULEDAYS: + case CONTROLLER_SCHEDULESTART: + case CONTROLLER_SCHEDULEEND: { + int index = getScheduleNumber(groupId) - 1; + + if (index < 0 || index >= NUM_SCHEDULES) { + return; + } + PentairControllerSchedule schedule = schedulesCache[index].getValue(() -> { + requestSchedule(index + 1); + }); + if (schedule != null) { + refreshScheduleChannels(channelUID, schedule); + } + break; + } + case CONTROLLER_CIRCUITNAME: + case CONTROLLER_CIRCUITFUNCTION: { + int circuitNumber = PentairControllerCircuit.getCircuitNumberByGroupID(groupId); + + if (circuitNumber <= 0 || circuitNumber > NUM_CIRCUITS) { + return; + } + PentairControllerCircuit circuit = circuitsCache[circuitNumber - 1].getValue(() -> { + requestCircuitNameFunction(circuitNumber); + }); + if (circuit != null) { + refreshCircuitChannels(channelUID, circuit); + } + break; + } + } + + return; + } + + logger.debug("handleCommand: {}", channelUID.getId()); + + switch (channelUID.getIdWithoutGroup()) { + case CONTROLLER_CIRCUITSWITCH: { + if (!(command instanceof OnOffType)) { + break; + } + PentairControllerCircuit circuit = getCircuitByGroupID(groupId); + if (circuit == null) { + break; + } + int circuitNum = circuit.id; + boolean state = ((OnOffType) command) == OnOffType.ON; + + circuitSwitch(circuitNum, state); + break; + } + case CONTROLLER_LIGHTMODE: { + if (!(command instanceof StringType)) { + break; + } + String str = ((StringType) command).toString(); + LightMode lightMode; + + try { + lightMode = PentairControllerConstants.LightMode.valueOf(str); + setLightMode(lightMode); + } catch (IllegalArgumentException e) { + logger.debug("Invalid light mode: {}", str); + } + break; + } + case CONTROLLER_SCHEDULESTRING: { + if (!(command instanceof StringType)) { + break; + } + PentairControllerSchedule schedule = getScheduleByGroupID(groupId); + + if (schedule == null) { + break; + } + String str = command.toString(); + + if (!schedule.fromString(str)) { + logger.debug("schedule invalid format: {}", str); + } + break; + } + case CONTROLLER_SCHEDULETYPE: { + if (!(command instanceof StringType)) { + break; + } + PentairControllerSchedule schedule = getScheduleByGroupID(groupId); + + if (schedule == null) { + break; + } + String str = ((StringType) command).toString(); + // save schedule only if a double update to the same value occurs within 5s of the last update + boolean bUpdate = (str.equals(schedule.getScheduleTypeStr()) + && ((System.currentTimeMillis() - lastScheduleTypeWrite) < 5000) && schedule.isDirty()); + if (!schedule.setScheduleType(str)) { + return; + } + lastScheduleTypeWrite = System.currentTimeMillis(); + if (bUpdate) { + saveSchedule(schedule); + lastScheduleTypeWrite = 0; + refreshScheduleChannels(channelUID, schedule); + } + break; + } + case CONTROLLER_SCHEDULESTART: { + if (!(command instanceof Number)) { + break; + } + + PentairControllerSchedule schedule = getScheduleByGroupID(groupId); + + if (schedule == null) { + break; + } + int start = ((Number) command).intValue(); + schedule.setScheduleStart(start); + break; + } + case CONTROLLER_SCHEDULEEND: { + if (!(command instanceof Number)) { + break; + } + PentairControllerSchedule schedule = getScheduleByGroupID(groupId); + if (schedule == null) { + break; + } + int end = ((Number) command).intValue(); + schedule.setScheduleEnd(end); + break; + } + case CONTROLLER_SCHEDULECIRCUIT: { + if (!(command instanceof Number)) { + break; + } + PentairControllerSchedule schedule = getScheduleByGroupID(groupId); + if (schedule == null) { + break; + } + int circuit = ((Number) command).intValue(); + schedule.setScheduleCircuit(circuit); + break; + } + case CONTROLLER_SCHEDULEDAYS: { + if (!(command instanceof StringType)) { + break; + } + PentairControllerSchedule schedule = getScheduleByGroupID(groupId); + if (schedule == null) { + break; + } + String days = ((StringType) command).toString(); + schedule.setDays(days); + break; + } + case CONTROLLER_SETPOINT: { + if (!(command instanceof QuantityType)) { + break; + } + QuantityType qt = (QuantityType) command; + switch (groupId) { + case CONTROLLER_SPAHEAT: + sendSetPoint(false, qt); + break; + case CONTROLLER_POOLHEAT: + sendSetPoint(true, qt); + break; + } + break; + } + case CONTROLLER_HEATERDELAY: { + if (!(command instanceof OnOffType)) { + break; + } + if ((OnOffType) command != OnOffType.OFF) { // Delay can only be cancelled + break; + } + cancelDelay(); + } + } + } + + /* Commands to send to Controller */ + + /** + * Method to turn on/off a circuit in response to a command from the framework + * + * @param circuit circuit number + * @param state + */ + public boolean circuitSwitch(int circuit, boolean state) { + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0x86, + (byte) 0x02, (byte) circuit, (byte) ((state) ? 1 : 0) }; + + logger.trace("circuit Switch: {}, {}", circuit, state); + + if (!writePacket(packet, 0x01, 1)) { + logger.trace("circuitSwitch: 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 requestStatus() { // A5 01 10 20 C2 01 00 + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0xC2, + (byte) 0x01, (byte) 0x00 }; + + logger.debug("Request controller status"); + if (!writePacket(packet, 0x02, 1)) { + logger.trace("requestStatus timeout"); + + return false; + } + + return true; + } + + /** + * Method to request clock + */ + public boolean requestClockSettings() { // A5 01 10 20 C5 01 00 + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0xC5, + (byte) 0x01, (byte) 0x00 }; + + logger.debug("Request clock settings"); + if (!writePacket(packet, 0x05, 1)) { + logger.trace("requestClockSetting: Timeout"); + + return false; + } + + return true; + } + + public void requestControllerStatus() { // A5 01 10 20 02 01 00 + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0x02, + (byte) 0x01, (byte) 0x00 }; + + logger.debug("Request controller status"); + + if (!writePacket(packet, 0x02, 1)) { + logger.trace("requestControllerStatus: Timeout"); + } + } + + public void requestLightGroups() { + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0xE7, + (byte) 0x01, (byte) 0x00 }; + + logger.debug("request Light Groups"); + + if (!writePacket(packet, 0x27, 1)) { + logger.trace("requestLightGroups: Timeout"); + } + } + + public void setLightMode(int mode) { + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0x60, + (byte) 0x02, (byte) mode, (byte) 0x00 }; + + logger.debug("setLightMode: {}", mode); + + if (!writePacket(packet, 0x01, 1)) { + logger.trace("setLightMode: Timeout"); + } + } + + public void setLightMode(PentairControllerConstants.LightMode lightMode) { + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0x60, + (byte) 0x02, (byte) lightMode.getModeNumber(), (byte) 0x00 }; + + logger.debug("setLightMode: {} ({})", lightMode.name(), lightMode.getModeNumber()); + + if (!writePacket(packet, 0x01, 1)) { + logger.trace("setLightMode: Timeout"); + } + } + + public void requestValves() { + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0xDD, + (byte) 0x01, (byte) 0x00 }; + + logger.debug("requestValves"); + + if (!writePacket(packet, 29, 1)) { + logger.trace("requestValves: Timeout"); + } + } + + public boolean requestCircuitNameFunction(int circuit) { + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0xCB, + (byte) 0x01, (byte) circuit }; + + logger.debug("requestCircuitNameFunction: {}", circuit); + + if (!writePacket(packet, 0x0B, 1)) { + logger.trace("requestCircuitNameFunction: Timeout"); + + return false; + } + return true; + } + + public boolean requestSchedule(int num) { + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0xD1, + (byte) 0x01, (byte) num }; + + logger.debug("requestSchedule: {}", num); + + if (!writePacket(packet, 0x11, 1)) { + logger.trace("requestSchedule: Timeout"); + + return false; + } + + return true; + } + + /** + * Method to update the schedule to the controller + * + * @param p + */ + public boolean saveSchedule(PentairControllerSchedule schedule) { + PentairPacket p; + + p = schedule.getWritePacket(id, preambleByte); + Objects.requireNonNull(p); + + logger.debug("saveSchedule: {}", p.toString()); + schedule.setDirty(false); + + if (!writePacket(p, 0x01, 1)) { + logger.trace("saveSchedule: Timeout"); + + return false; + } + + return true; + } + + public boolean requestSWVersion() { + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0xFD, + (byte) 0x01, (byte) 0x00 }; + + logger.debug("requestSWVersion"); + + if (!writePacket(packet, 0xFC, 1)) { + logger.trace("requestSWVersion: Timeout"); + return false; + } + + String version = String.format("%d.%d", majorrev, minorrev); + + Map editProperties = editProperties(); + editProperties.put(CONTROLLER_PROPERTYFWVERSION, version); + updateProperties(editProperties); + + return true; + } + + public boolean cancelDelay() { + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0x83, + (byte) 0x01, (byte) 0x00 }; + + logger.debug("cancelDelay"); + + if (!writePacket(packet, 1, 1)) { + logger.trace("cancelDelay: Timeout"); + return false; + } + + return true; + } + + /** + * Method to set clock + * + */ + public void sendClockSettings(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 + + logger.debug("Send Clock Settings {}:{} {} {}/{}/{}", hour, min, dow, day, month, year); + + 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) 0x85, + (byte) 0x08, (byte) hour, (byte) min, (byte) dow, (byte) day, (byte) month, (byte) year, (byte) 0x00, + (byte) 0x00 }; + + writePacket(packet); + } + + public void requestHeatStatus() { // A5 01 10 20 C8 01 00 + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0xC8, + (byte) 0x01, (byte) 0 }; + + logger.debug("request heat settings"); + + if (!writePacket(packet, 0x08, 1)) { + logger.trace("requestHeat: Timeout"); + } + } + + /** + * Method to set heat point for pool (true) of spa (false) + * + * @param Pool pool=true, spa=false + * @param temp + */ + public void sendSetPoint(boolean pool, QuantityType 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 value; + + QuantityType t; + if (temp.getUnit() == SIUnits.CELSIUS) { + value = Math.max(10, Math.min(temp.intValue(), 41)); + t = QuantityType.valueOf(value, SIUnits.CELSIUS); + } else if (temp.getUnit() == ImperialUnits.FAHRENHEIT) { + value = Math.max(50, Math.min(temp.intValue(), 105)); + t = QuantityType.valueOf(value, ImperialUnits.FAHRENHEIT); + } else { + logger.debug("sendSetPoint QuantityType not a temperature"); + return; + } + + t = (getUOM() == SIUnits.CELSIUS) ? t.toUnit(SIUnits.CELSIUS) : t.toUnit(ImperialUnits.FAHRENHEIT); + if (t == null) { + return; + } + + PentairHeatStatus heatStatus = heatStatusCache.getLastKnownValue(); + if (heatStatus == null) { + return; + } + + int spaset = (!pool) ? t.intValue() : heatStatus.spaSetPoint; + int poolset = (pool) ? t.intValue() : heatStatus.poolSetPoint; + Objects.requireNonNull(heatStatus.spaHeatMode); + Objects.requireNonNull(heatStatus.poolHeatMode); + int heatmode = (heatStatus.spaHeatMode.getCode() << 2) | heatStatus.poolHeatMode.getCode(); + + byte[] packet = { (byte) 0xA5, (byte) preambleByte, (byte) id, (byte) 0x00 /* source */, (byte) 0x88, + (byte) 0x04, (byte) poolset, (byte) spaset, (byte) heatmode, (byte) 0 }; + + logger.debug("Set {} temperature: {}", (pool) ? "Pool" : "Spa", temp); + + if (!writePacket(packet, 0x01, 1)) { + logger.trace("sendSetPoint: Timeout"); + } + } + + @Override + public void processPacketFrom(PentairPacket p) { + switch (p.getAction()) { + case 1: // Ack + logger.debug("Ack command from device: {} - {}", p.getByte(0), p); + break; + case 2: // Controller Status + if (p.getLength() != 29) { + logger.debug("Expected length of 29: {}", p); + return; + } + + logger.trace("Controller Status: {}", p); + + preambleByte = p.getPreambleByte(); // Adjust what byte is used for preamble + 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)) { + refreshControllerStatusChannels(null, newControllerStatus); + } + + break; + case 4: // Pump control panel on/off - handled in intelliflo controller + // Controller sends packet often to keep control of the motor + logger.debug("Pump control panel on/of {}: {}", p.getDest(), p.getByte(0)); + + break; + case 5: // 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); + int minute = p.getByte(1); + int dow = p.getByte(2); + int day = p.getByte(3); + int month = p.getByte(4); + int year = p.getByte(5); + + logger.debug("System Clock: {}:{} {} {}/{}/{}", hour, minute, dow, day, month, year); + + break; + case 6: // Set run mode + // No action - have not verified these commands, here for documentation purposes and future enhancement + logger.debug("Set run mode {}: {}", p.getDest(), p.getByte(0)); + + break; + case 7: // Pump Status - handled in IntelliFlo handler + // No action - have not verified these commands, here for documentation purposes and future enhancement + logger.debug("Pump request status (unseen): {}", p); + break; + case 8: // Heat Status - 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; + } + + PentairHeatStatus heatStatus = new PentairHeatStatus(p); + heatStatusCache.putValue(heatStatus); + + refreshHeatStatusChannels(null, heatStatus); + + logger.debug("Heat status: {}, {}, {}", p, heatStatus.poolSetPoint, heatStatus.spaSetPoint); + break; + case 10: // Custom Names + logger.debug("Get Custom Names (unseen): {}", p); + break; + case 11: // Circuit Names + int index; + + index = p.getByte(0); + index--; // zero index + if (index < 0 || index >= NUM_CIRCUITS) { + break; + } + + PentairControllerCircuit circuit = new PentairControllerCircuit(index + 1, CIRCUIT_GROUPS[index]); + + circuit.setName(p.getByte(2)); + circuit.setFunction(p.getByte(1)); + + refreshCircuitChannels(null, circuit); + + circuitsCache[index].putValue(circuit); + + logger.debug("Circuit Names - Circuit: {}, Function: {}, Name: {}", circuit.id, + circuit.circuitFunction.getFriendlyName(), circuit.circuitName.getFriendlyName()); + break; + case 17: // schedule - A5 1E 0F 10 11 07 01 06 0B 00 0F 00 7F + int id; + + id = p.getByte(PentairControllerSchedule.ID); + if (id < 1 || id > NUM_SCHEDULES) { + break; + } + + PentairControllerSchedule schedule = new PentairControllerSchedule(); + + schedule.parsePacket(p); + + String groupID = schedule.getGroupID(); + + logger.debug("Controller schedule group: {}", groupID); + + refreshScheduleChannels(null, schedule); + + schedulesCache[id - 1].putValue(schedule); + + logger.debug( + "Controller Schedule - ID: {}, Type: {}, Circuit: {}, Start Time: {}:{}, End Time: {}:{}, Days: {}", + schedule.id, schedule.type, schedule.circuit, schedule.start / 60, schedule.start % 60, + schedule.end / 60, schedule.end % 60, schedule.days); + break; + case 18: // IntelliChem + logger.debug("IntelliChem status: {}", p); + break; + case 25: // Intellichlor status + logger.debug("Intellichlor status: {}", p); + break; + case 27: // Pump config (Extended) + logger.debug("Pump Config: {}", p); + break; + case 29: // Valves + logger.debug("Values: {}", p); + break; + case 30: // High speed circuits + logger.debug("High speed circuits: {}", p); + break; + case 32: // spa-side is4/is10 remote + case 33: // spa-side quicktouch remotes + logger.debug("Spa-side remotes: {}", p); + break; + case 34: // Solar/Heat Pump status + logger.debug("Solar/Heat Pump status: {}", p); + break; + case 35: // Delay status + logger.debug("Delay status: {}", p); + break; + case 39: // Light Groups/Positions + logger.debug("Light Groups/Positions; {}", p); + break; + case 40: // Settings? heat mode + logger.debug("Settings?: {}", p); + break; + case 96: // set intellebrite colors + logger.debug("Set intellebrite colors: {}", p); + break; + case 134: // Set Curcuit On/Off + logger.debug("Set Circuit Function On/Off (unseen): {}", p); + break; + case 210: // Get Intellichem status + logger.debug("Get IntelliChem status: {}", p); + break; + case 252: // 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); + minorrev = p.getByte(2); + logger.debug("SW Version - {}:{}", majorrev, minorrev); + break; + default: + logger.debug("Not Implemented {}: {}", p.getAction(), p); + break; + } + } + + /** + * Helper function to update channel. + */ + public void updateChannel(@Nullable String group, String channel, boolean value) { + Objects.nonNull(group); + updateState(group + "#" + channel, (value) ? OnOffType.ON : OnOffType.OFF); + } + + public void updateChannelTemp(@Nullable String group, String channel, int value) { + Objects.nonNull(group); + if (value != 999) { + updateState(group + "#" + channel, new QuantityType(value, getUOM())); + } else { + updateState(group + "#" + channel, UnDefType.UNDEF); + } + } + + public void updateChannel(@Nullable String group, String channel, int value) { + Objects.nonNull(group); + updateState(group + "#" + channel, new DecimalType(value)); + } + + public void updateChannel(@Nullable String group, String channel, @Nullable String value) { + Objects.nonNull(group); + if (value == null) { + updateState(group + "#" + channel, UnDefType.NULL); + } else { + updateState(group + "#" + channel, new StringType(value)); + } + } + + public void refreshScheduleChannels(@Nullable ChannelUID fullChannelUID, PentairControllerSchedule schedule) { + String groupID = ""; + String channelID = ""; + + if (fullChannelUID != null) { + groupID = Objects.requireNonNull(fullChannelUID.getGroupId()); + channelID = fullChannelUID.getIdWithoutGroup(); + + if (!schedule.getGroupID().equals(groupID)) { + // this should never happen + logger.debug("refreshScheduleChannels: schedule group is not aligned with channel"); + } + } + + if (fullChannelUID == null || channelID.equals(CONTROLLER_SCHEDULESTRING)) { + updateChannel(schedule.getGroupID(), CONTROLLER_SCHEDULESTRING, schedule.toString()); + } + + if (fullChannelUID == null || channelID.equals(CONTROLLER_SCHEDULETYPE)) { + logger.debug("groupID: {}, scheduleType: {}", schedule.getGroupID(), schedule.getScheduleTypeStr()); + updateChannel(schedule.getGroupID(), CONTROLLER_SCHEDULETYPE, schedule.getScheduleTypeStr()); + } + + if (fullChannelUID == null || channelID.equals(CONTROLLER_SCHEDULECIRCUIT)) { + updateChannel(schedule.getGroupID(), CONTROLLER_SCHEDULECIRCUIT, schedule.circuit); + } + + if (fullChannelUID == null || channelID.equals(CONTROLLER_SCHEDULESTART)) { + updateChannel(schedule.getGroupID(), CONTROLLER_SCHEDULESTART, schedule.start); + } + + if (fullChannelUID == null || channelID.equals(CONTROLLER_SCHEDULEEND)) { + updateChannel(schedule.getGroupID(), CONTROLLER_SCHEDULEEND, schedule.end); + } + + if (fullChannelUID == null || channelID.equals(CONTROLLER_SCHEDULEDAYS)) { + updateChannel(schedule.getGroupID(), CONTROLLER_SCHEDULEDAYS, schedule.getDays()); + } + + logger.debug( + "Controller Schedule - ID: {}, Type: {}, Circuit: {}, Start Time: {}:{}, End Time: {}:{}, Days: {}", + schedule.id, schedule.type, schedule.circuit, schedule.start / 60, schedule.start % 60, + schedule.end / 60, schedule.end % 60, schedule.getDays()); + } + + public void refreshControllerStatusChannels(@Nullable ChannelUID fullChannelUID, + PentairControllerStatus controllerStatus) { + String groupID = ""; + String channelID = ""; + + boolean updateAll = fullChannelUID == null; + + if (!updateAll) { + groupID = Objects.requireNonNull(fullChannelUID.getGroupId()); + channelID = fullChannelUID.getIdWithoutGroup(); + } + + // update circuit states + if (channelID.equals(CONTROLLER_CIRCUITSWITCH)) { + PentairControllerCircuit circuit; + + circuit = getCircuitByGroupID(groupID); + if (circuit == null) { + return; + } + + int circuitNum = circuit.id - 1; + updateChannel(groupID, CONTROLLER_CIRCUITSWITCH, controllerStatus.circuits[circuitNum]); + } else if (updateAll) { + for (int i = 0; i < NUM_CIRCUITS; i++) { + String circuitGroupID; + + circuitGroupID = PentairControllerCircuit.getCircuitGroupIDByNumber(i + 1); + + if (circuitGroupID != null) { + updateChannel(circuitGroupID, CONTROLLER_CIRCUITSWITCH, controllerStatus.circuits[i]); + } + } + } + + if (updateAll || channelID.equals(CONTROLLER_UOM)) { + updateChannel(CONTROLLER_STATUS, CONTROLLER_UOM, (controllerStatus.uom) ? "CELCIUS" : "FAHRENHEIT"); + this.uom = controllerStatus.uom; + } + + if (updateAll || channelID.equals(CONTROLLER_TEMPERATURE)) { + updateChannelTemp(CONTROLLER_POOLHEAT, CONTROLLER_TEMPERATURE, + (controllerStatus.pool) ? controllerStatus.poolTemp : 999); + updateChannelTemp(CONTROLLER_SPAHEAT, CONTROLLER_TEMPERATURE, + (controllerStatus.spa) ? controllerStatus.spaTemp : 999); + } + + if (updateAll || channelID.equals(CONTROLLER_AIRTEMPERATURE)) { + updateChannelTemp(CONTROLLER_STATUS, CONTROLLER_AIRTEMPERATURE, controllerStatus.airTemp); + } + + if (updateAll || channelID.equals(CONTROLLER_SOLARTEMPERATURE)) { + updateChannelTemp(CONTROLLER_STATUS, CONTROLLER_SOLARTEMPERATURE, controllerStatus.solarTemp); + } + + if (updateAll || channelID.equals(CONTROLLER_SERVICEMODE)) { + updateChannel(CONTROLLER_STATUS, CONTROLLER_SERVICEMODE, controllerStatus.serviceMode); + serviceMode = controllerStatus.serviceMode; + } + + if (updateAll || channelID.equals(CONTROLLER_HEATERON)) { + updateChannel(CONTROLLER_STATUS, CONTROLLER_HEATERON, controllerStatus.heaterOn); + } + + if (updateAll || channelID.equals(CONTROLLER_SOLARON)) { + updateChannel(CONTROLLER_STATUS, CONTROLLER_SOLARON, controllerStatus.solarOn); + } + + if (updateAll || channelID.equals(CONTROLLER_HEATERDELAY)) { + updateChannel(CONTROLLER_STATUS, CONTROLLER_HEATERDELAY, controllerStatus.heaterDelay); + } + } + + public void refreshHeatStatusChannels(@Nullable ChannelUID fullChannelUID, PentairHeatStatus heatStatus) { + String groupID = ""; + String channelID = ""; + boolean updateAll = fullChannelUID == null; + + if (!updateAll) { + groupID = Objects.requireNonNull(fullChannelUID.getGroupId()); + channelID = fullChannelUID.getIdWithoutGroup(); + } + + if (updateAll || groupID.equals(CONTROLLER_POOLHEAT)) { + if (updateAll || channelID.equals(CONTROLLER_SETPOINT)) { + updateChannelTemp(CONTROLLER_POOLHEAT, CONTROLLER_SETPOINT, heatStatus.poolSetPoint); + } + + if ((updateAll || channelID.equals(CONTROLLER_HEATMODE)) && heatStatus.poolHeatMode != null) { + updateChannel(CONTROLLER_POOLHEAT, CONTROLLER_HEATMODE, heatStatus.poolHeatMode.name()); + } + } + + if (updateAll || groupID.equals(CONTROLLER_SPAHEAT)) { + if (updateAll || channelID.equals(CONTROLLER_SETPOINT)) { + updateChannelTemp(CONTROLLER_SPAHEAT, CONTROLLER_SETPOINT, heatStatus.spaSetPoint); + } + + if ((updateAll || channelID.equals(CONTROLLER_HEATMODE)) && heatStatus.spaHeatMode != null) { + updateChannel(CONTROLLER_SPAHEAT, CONTROLLER_HEATMODE, heatStatus.spaHeatMode.name()); + } + } + } + + public void refreshCircuitChannels(@Nullable ChannelUID fullChannelUID, PentairControllerCircuit circuit) { + String groupID = ""; + String channelID = ""; + boolean updateAll = fullChannelUID == null; + + if (!updateAll) { + groupID = Objects.requireNonNull(fullChannelUID.getGroupId()); + channelID = fullChannelUID.getIdWithoutGroup(); + + if (!circuit.getGroupID().equals(groupID)) { + // this should never happen + logger.debug("refreshCircuitChannels: circuit group is not aligned with channel"); + } + } + + if (updateAll || channelID.equals(CONTROLLER_CIRCUITNAME)) { + updateChannel(circuit.getGroupID(), CONTROLLER_CIRCUITNAME, circuit.circuitName.getFriendlyName()); + } + + if (updateAll || channelID.equals(CONTROLLER_CIRCUITFUNCTION)) { + updateChannel(circuit.getGroupID(), CONTROLLER_CIRCUITFUNCTION, circuit.circuitFunction.getFriendlyName()); + } + } +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairEasyTouchHandler.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairEasyTouchHandler.java deleted file mode 100644 index 9fedff4194e01..0000000000000 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairEasyTouchHandler.java +++ /dev/null @@ -1,497 +0,0 @@ -/** - * Copyright (c) 2010-2020 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.pentair.internal.handler; - -import static org.openhab.binding.pentair.internal.PentairBindingConstants.*; - -import java.math.BigDecimal; -import java.util.List; - -import org.openhab.binding.pentair.internal.PentairBindingConstants; -import org.openhab.binding.pentair.internal.PentairPacket; -import org.openhab.binding.pentair.internal.PentairPacketHeatSetPoint; -import org.openhab.binding.pentair.internal.PentairPacketStatus; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.UnDefType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link PentairEasyTouchHandler} is responsible for implementation of the EasyTouch Controller. It will handle - * commands sent to a thing and implements the different channels. It also parses/disposes of the packets seen on the - * bus from the controller. - * - * @author Jeff James - Initial contribution - */ -public class PentairEasyTouchHandler extends PentairBaseThingHandler { - - private final Logger logger = LoggerFactory.getLogger(PentairEasyTouchHandler.class); - - /** - * current/last status packet recieved, used to compare new packet values to determine if status needs to be updated - */ - protected PentairPacketStatus p29cur = new PentairPacketStatus(); - /** current/last heat set point packet, used to determine if status in framework should be updated */ - protected PentairPacketHeatSetPoint phspcur = new PentairPacketHeatSetPoint(); - - public PentairEasyTouchHandler(Thing thing) { - super(thing); - } - - @Override - public void initialize() { - logger.debug("Initializing EasyTouch - Thing ID: {}.", this.getThing().getUID()); - - id = ((BigDecimal) getConfig().get("id")).intValue(); - - // make sure there are no exisitng EasyTouch controllers - PentairBaseBridgeHandler bh = (PentairBaseBridgeHandler) this.getBridge().getHandler(); - List things = bh.getThing().getThings(); - - for (Thing t : things) { - if (t.getUID().equals(this.getThing().getUID())) { - continue; - } - if (t.getThingTypeUID().equals(EASYTOUCH_THING_TYPE)) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "Another EasyTouch controller is already configured."); - return; - } - } - - updateStatus(ThingStatus.ONLINE); - } - - @Override - public void dispose() { - logger.debug("Thing {} disposed.", getThing().getUID()); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - // When channel gets a refresh request, sending a null as the PentairPacket to updateChannel will force an - // updateState, regardless of previous packet value - if (command instanceof RefreshType) { - logger.debug("EasyTouch received refresh command"); - - updateChannel(channelUID.getId(), null); - - return; - } - - if (command instanceof OnOffType) { - boolean state = ((OnOffType) command) == OnOffType.ON; - - switch (channelUID.getId()) { - case EASYTOUCH_POOL: - circuitSwitch(6, state); - break; - case EASYTOUCH_SPA: - circuitSwitch(1, state); - break; - case EASYTOUCH_AUX1: - circuitSwitch(2, state); - break; - case EASYTOUCH_AUX2: - circuitSwitch(3, state); - break; - case EASYTOUCH_AUX3: - circuitSwitch(4, state); - break; - case EASYTOUCH_AUX4: - circuitSwitch(5, state); - break; - case EASYTOUCH_AUX5: - circuitSwitch(7, state); - break; - case EASYTOUCH_AUX6: - circuitSwitch(8, state); - break; - case EASYTOUCH_AUX7: // A5 01 10 20 86 02 09 01 - circuitSwitch(9, state); - break; - case EASYTOUCH_FEATURE1: - circuitSwitch(11, state); - break; - case EASYTOUCH_FEATURE2: - circuitSwitch(12, state); - break; - case EASYTOUCH_FEATURE3: - circuitSwitch(13, state); - break; - case EASYTOUCH_FEATURE4: - circuitSwitch(14, state); - break; - case EASYTOUCH_FEATURE5: - circuitSwitch(15, state); - break; - case EASYTOUCH_FEATURE6: - circuitSwitch(16, state); - break; - case EASYTOUCH_FEATURE7: - circuitSwitch(17, state); - break; - case EASYTOUCH_FEATURE8: - circuitSwitch(18, state); - break; - } - } else if (command instanceof DecimalType) { - int sp = ((DecimalType) command).intValue(); - - switch (channelUID.getId()) { - case EASYTOUCH_SPASETPOINT: - setPoint(false, sp); - break; - case EASYTOUCH_POOLSETPOINT: - setPoint(true, sp); - break; - } - } - } - - /** - * Method to turn on/off a circuit in response to a command from the framework - * - * @param circuit circuit number - * @param state - */ - public void circuitSwitch(int circuit, boolean state) { - byte[] packet = { (byte) 0xA5, (byte) 0x01, (byte) id, (byte) 0x00 /* source */, (byte) 0x86, (byte) 0x02, - (byte) circuit, (byte) ((state) ? 1 : 0) }; - - PentairPacket p = new PentairPacket(packet); - - PentairBaseBridgeHandler bbh = (PentairBaseBridgeHandler) this.getBridge().getHandler(); - bbh.writePacket(p); - } - - /** - * Method to set heat point for pool (true) of spa (false) - * - * @param Pool pool=true, spa=false - * @param temp - */ - public void setPoint(boolean pool, int temp) { - // [16,34,136,4,POOL HEAT Temp,SPA HEAT Temp,Heat Mode,0,2,56] - // [165, preambleByte, 16, 34, 136, 4, currentHeat.poolSetPoint, parseInt(req.params.temp), updateHeatMode, 0] - int spaset = (!pool) ? temp : phspcur.spasetpoint; - int poolset = (pool) ? temp : phspcur.poolsetpoint; - int heatmode = (phspcur.spaheatmode << 2) | phspcur.poolheatmode; - - byte[] packet = { (byte) 0xA5, (byte) 0x01, (byte) id, (byte) 0x00 /* source */, (byte) 0x88, (byte) 0x04, - (byte) poolset, (byte) spaset, (byte) heatmode, (byte) 0 }; - - logger.info("Set {} temperature: {}", (pool) ? "Pool" : "Spa", temp); - - PentairPacket p = new PentairPacket(packet); - - PentairBaseBridgeHandler bbh = (PentairBaseBridgeHandler) this.getBridge().getHandler(); - bbh.writePacket(p); - } - - @Override - public void processPacketFrom(PentairPacket p) { - switch (p.getAction()) { - case 1: // Write command to pump - logger.trace("Write command to pump (unimplemented): {}", p); - break; - case 2: - if (p.getLength() != 29) { - logger.debug("Expected length of 29: {}", p); - return; - } - - /* - * Save the previous state of the packet (p29cur) into a temp variable (p29old) - * Update the current state to the new packet we just received. - * Then call updateChannel which will compare the previous state (now p29old) to the new state (p29cur) - * to determine if updateState needs to be called - */ - PentairPacketStatus p29Old = p29cur; - p29cur = new PentairPacketStatus(p); - - updateChannel(EASYTOUCH_POOL, p29Old); - updateChannel(EASYTOUCH_POOLTEMP, p29Old); - updateChannel(EASYTOUCH_SPATEMP, p29Old); - updateChannel(EASYTOUCH_AIRTEMP, p29Old); - updateChannel(EASYTOUCH_SOLARTEMP, p29Old); - updateChannel(EASYTOUCH_HEATACTIVE, p29Old); - updateChannel(EASYTOUCH_POOL, p29Old); - updateChannel(EASYTOUCH_SPA, p29Old); - updateChannel(EASYTOUCH_AUX1, p29Old); - updateChannel(EASYTOUCH_AUX2, p29Old); - updateChannel(EASYTOUCH_AUX3, p29Old); - updateChannel(EASYTOUCH_AUX4, p29Old); - updateChannel(EASYTOUCH_AUX5, p29Old); - updateChannel(EASYTOUCH_AUX6, p29Old); - updateChannel(EASYTOUCH_AUX7, p29Old); - updateChannel(EASYTOUCH_FEATURE1, p29Old); - updateChannel(EASYTOUCH_FEATURE2, p29Old); - updateChannel(EASYTOUCH_FEATURE3, p29Old); - updateChannel(EASYTOUCH_FEATURE4, p29Old); - updateChannel(EASYTOUCH_FEATURE5, p29Old); - updateChannel(EASYTOUCH_FEATURE6, p29Old); - updateChannel(EASYTOUCH_FEATURE7, p29Old); - updateChannel(EASYTOUCH_FEATURE8, p29Old); - updateChannel(DIAG, p29Old); - - break; - case 4: // Pump control panel on/off - // No action - have not verified these commands, here for documentation purposes and future enhancement - logger.trace("Pump control panel on/of {}: {}", p.getDest(), p.getByte(PentairPacket.STARTOFDATA)); - - break; - case 5: // Set pump mode - // No action - have not verified these commands, here for documentation purposes and future enhancement - logger.trace("Set pump mode {}: {}", p.getDest(), p.getByte(PentairPacket.STARTOFDATA)); - - break; - case 6: // Set run mode - // No action - have not verified these commands, here for documentation purposes and future enhancement - logger.trace("Set run mode {}: {}", p.getDest(), p.getByte(PentairPacket.STARTOFDATA)); - - break; - case 7: - // No action - have not verified these commands, here for documentation purposes and future enhancement - logger.trace("Pump request status (unseen): {}", p); - break; - case 8: // A5 01 0F 10 08 0D 4B 4B 4D 55 5E 07 00 00 58 00 00 00  - if (p.getLength() != 0x0D) { - logger.debug("Expected length of 13: {}", p); - return; - } - - /* - * Save the previous state of the packet (phspcur) into a temp variable (phspOld) - * Update the current state to the new packet we just received. - * Then call updateChannel which will compare the previous state (now phspold) to the new state - * (phspcur) to determine if updateState needs to be called - */ - PentairPacketHeatSetPoint phspOld = phspcur; - phspcur = new PentairPacketHeatSetPoint(p); - - updateChannel(EASYTOUCH_POOLSETPOINT, phspOld); - updateChannel(EASYTOUCH_SPASETPOINT, phspOld); - updateChannel(EASYTOUCH_SPAHEATMODE, phspOld); - updateChannel(EASYTOUCH_SPAHEATMODESTR, phspOld); - updateChannel(EASYTOUCH_POOLHEATMODE, phspOld); - updateChannel(EASYTOUCH_POOLHEATMODESTR, phspOld); - - logger.debug("Heat set point: {}, {}, {}", p, phspcur.poolsetpoint, phspcur.spasetpoint); - break; - case 10: - logger.debug("Get Custom Names (unseen): {}", p); - break; - case 11: - logger.debug("Get Ciruit Names (unseen): {}", p); - break; - case 17: - logger.debug("Get Schedules (unseen): {}", p); - break; - case 134: - logger.debug("Set Circuit Function On/Off (unseen): {}", p); - break; - default: - logger.debug("Not Implemented: {}", p); - break; - } - } - - /** - * Helper function to compare and update channel if needed. The class variables p29_cur and phsp_cur are used to - * determine the appropriate state of the channel. - * - * @param channel name of channel to be updated, corresponds to channel name in {@link PentairBindingConstants} - * @param p Packet representing the former state. If null, no compare is done and state is updated. - */ - public void updateChannel(String channel, PentairPacket p) { - PentairPacketStatus p29 = null; - PentairPacketHeatSetPoint phsp = null; - - if (p != null) { - if (p.getLength() == 29) { - p29 = (PentairPacketStatus) p; - } else if (p.getLength() == 13) { - phsp = (PentairPacketHeatSetPoint) p; - } - } - - switch (channel) { - case EASYTOUCH_POOL: - if (p29 == null || (p29.pool != p29cur.pool)) { - updateState(channel, (p29cur.pool) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_SPA: - if (p29 == null || (p29.spa != p29cur.spa)) { - updateState(channel, (p29cur.spa) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_AUX1: - if (p29 == null || (p29.aux1 != p29cur.aux1)) { - updateState(channel, (p29cur.aux1) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_AUX2: - if (p29 == null || (p29.aux2 != p29cur.aux2)) { - updateState(channel, (p29cur.aux2) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_AUX3: - if (p29 == null || (p29.aux3 != p29cur.aux3)) { - updateState(channel, (p29cur.aux3) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_AUX4: - if (p29 == null || (p29.aux4 != p29cur.aux4)) { - updateState(channel, (p29cur.aux4) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_AUX5: - if (p29 == null || (p29.aux5 != p29cur.aux5)) { - updateState(channel, (p29cur.aux5) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_AUX6: - if (p29 == null || (p29.aux6 != p29cur.aux6)) { - updateState(channel, (p29cur.aux6) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_AUX7: - if (p29 == null || (p29.aux7 != p29cur.aux7)) { - updateState(channel, (p29cur.aux7) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_FEATURE1: - if (p29 == null || (p29.feature1 != p29cur.feature1)) { - updateState(channel, (p29cur.feature1) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_FEATURE2: - if (p29 == null || (p29.feature2 != p29cur.feature2)) { - updateState(channel, (p29cur.feature2) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_FEATURE3: - if (p29 == null || (p29.feature3 != p29cur.feature3)) { - updateState(channel, (p29cur.feature3) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_FEATURE4: - if (p29 == null || (p29.feature4 != p29cur.feature4)) { - updateState(channel, (p29cur.feature4) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_FEATURE5: - if (p29 == null || (p29.feature5 != p29cur.feature5)) { - updateState(channel, (p29cur.feature5) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_FEATURE6: - if (p29 == null || (p29.feature6 != p29cur.feature6)) { - updateState(channel, (p29cur.feature6) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_FEATURE7: - if (p29 == null || (p29.feature7 != p29cur.feature7)) { - updateState(channel, (p29cur.feature7) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_FEATURE8: - if (p29 == null || (p29.feature8 != p29cur.feature8)) { - updateState(channel, (p29cur.feature8) ? OnOffType.ON : OnOffType.OFF); - } - break; - case EASYTOUCH_POOLTEMP: - if (p29 == null || (p29.pooltemp != p29cur.pooltemp)) { - if (p29cur.pool) { - updateState(channel, new DecimalType(p29cur.pooltemp)); - } else { - updateState(channel, UnDefType.UNDEF); - } - } - break; - case EASYTOUCH_SPATEMP: - if (p29 == null || (p29.spatemp != p29cur.spatemp)) { - if (p29cur.spa) { - updateState(channel, new DecimalType(p29cur.spatemp)); - } else { - updateState(channel, UnDefType.UNDEF); - } - } - break; - case EASYTOUCH_AIRTEMP: - if (p29 == null || (p29.airtemp != p29cur.airtemp)) { - updateState(channel, new DecimalType(p29cur.airtemp)); - } - break; - case EASYTOUCH_SOLARTEMP: - if (p29 == null || (p29.solartemp != p29cur.solartemp)) { - updateState(channel, new DecimalType(p29cur.solartemp)); - } - break; - case EASYTOUCH_SPAHEATMODE: - if (phsp == null || (phsp.spaheatmode != phspcur.spaheatmode)) { - updateState(channel, new DecimalType(phspcur.spaheatmode)); - } - break; - case EASYTOUCH_SPAHEATMODESTR: - if (phsp == null || (phsp.spaheatmodestr != phspcur.spaheatmodestr)) { - if (phspcur.spaheatmodestr != null) { - updateState(channel, new StringType(phspcur.spaheatmodestr)); - } - } - break; - case EASYTOUCH_POOLHEATMODE: - if (phsp == null || (phsp.poolheatmode != phspcur.poolheatmode)) { - updateState(channel, new DecimalType(phspcur.poolheatmode)); - } - break; - case EASYTOUCH_POOLHEATMODESTR: - if (phsp == null || (phsp.poolheatmodestr != phspcur.poolheatmodestr)) { - if (phspcur.poolheatmodestr != null) { - updateState(channel, new StringType(phspcur.poolheatmodestr)); - } - } - break; - case EASYTOUCH_HEATACTIVE: - if (p29 == null || (p29.heatactive != p29cur.heatactive)) { - updateState(channel, new DecimalType(p29cur.heatactive)); - } - break; - case EASYTOUCH_POOLSETPOINT: - if (phsp == null || (phsp.poolsetpoint != phspcur.poolsetpoint)) { - updateState(channel, new DecimalType(phspcur.poolsetpoint)); - } - break; - case EASYTOUCH_SPASETPOINT: - if (phsp == null || (phsp.spasetpoint != phspcur.spasetpoint)) { - updateState(channel, new DecimalType(phspcur.spasetpoint)); - } - break; - case DIAG: - if (p29 == null || (p29.diag != p29cur.diag)) { - updateState(channel, new DecimalType(p29cur.diag)); - } - break; - } - } -} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIPBridgeHandler.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIPBridgeHandler.java index ae6b0a4e5b121..245b4519800c2 100644 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIPBridgeHandler.java +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIPBridgeHandler.java @@ -12,12 +12,12 @@ */ package org.openhab.binding.pentair.internal.handler; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; +import org.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; @@ -31,86 +31,57 @@ * @author Jeff James - Initial contribution * */ + +@NonNullByDefault public class PentairIPBridgeHandler extends PentairBaseBridgeHandler { private final Logger logger = LoggerFactory.getLogger(PentairIPBridgeHandler.class); + public PentairIPBridgeConfig config = new PentairIPBridgeConfig(); + /** Socket object for connection */ - protected Socket socket; + protected @Nullable Socket socket; public PentairIPBridgeHandler(Bridge bridge) { super(bridge); } @Override - protected synchronized void connect() { - PentairIPBridgeConfig configuration = getConfigAs(PentairIPBridgeConfig.class); + protected synchronized int connect() { + config = getConfigAs(PentairIPBridgeConfig.class); - id = configuration.id; + this.id = config.id; + this.discovery = config.discovery; 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); + Socket socket = new Socket(config.address, config.port); + this.socket = socket; + + 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); + String msg = String.format("unknown host name: %s", config.address); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg); - return; + return -1; } catch (IOException e) { - String msg = String.format("cannot open connection to %s", configuration.address); + String msg = String.format("cannot open connection to %s", config.address); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg); - return; + return -2; } - 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 0; } @Override protected synchronized void disconnect() { updateStatus(ThingStatus.OFFLINE); - if (thread != null) { - try { - thread.interrupt(); - thread.join(); // wait for thread to complete - } catch (InterruptedException e) { - // do nothing - } - thread = null; - parser = null; - } - - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error in closing reader"); - } - reader = null; - } - - if (writer != null) { - try { - writer.close(); - } catch (IOException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error in closing writer"); - } - writer = null; - } - if (socket != null) { try { socket.close(); } catch (IOException e) { - logger.error("error when closing socket ", e); + logger.debug("error when closing socket ", e); } socket = null; } diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChemHandler.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChemHandler.java new file mode 100644 index 0000000000000..6b1e59a15f954 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChemHandler.java @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal.handler; + +import static org.openhab.binding.pentair.internal.PentairBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.pentair.internal.PentairIntelliChem; +import org.openhab.binding.pentair.internal.PentairPacket; +import org.openhab.binding.pentair.internal.config.PentairIntelliChemHandlerConfig; +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 IntelliChemp. This will + * parse of status packets to set the stat for various channels. + * + * @author Jeff James - Initial contribution + */ +@NonNullByDefault +public class PentairIntelliChemHandler extends PentairBaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(PentairIntelliChemHandler.class); + + protected PentairIntelliChem pic = new PentairIntelliChem(); + + public PentairIntelliChemHandler(Thing thing) { + super(thing); + } + + @Override + public void readConfiguration() { + PentairIntelliChemHandlerConfig config = getConfigAs(PentairIntelliChemHandlerConfig.class); + + this.id = config.id; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.debug("handleCommand, {}, {}", channelUID, command); + + // all fields are read only + if (!(command instanceof RefreshType)) { + return; + } + + // The IntelliChem routinely updates the state, so just refresh to last state + switch (channelUID.getId()) { + case INTELLICHEM_PHREADING: + updateChannel(INTELLICHEM_PHREADING, pic.phReading); + break; + case INTELLICHEM_ORPREADING: + updateChannel(INTELLICHEM_ORPREADING, pic.orpReading); + break; + case INTELLICHEM_PHSETPOINT: + updateChannel(INTELLICHEM_PHSETPOINT, pic.phSetPoint); + break; + case INTELLICHEM_ORPSETPOINT: + updateChannel(INTELLICHEM_ORPSETPOINT, pic.orpSetPoint); + break; + case INTELLICHEM_TANK1: + updateChannel(INTELLICHEM_TANK1, pic.tank1); + break; + case INTELLICHEM_TANK2: + updateChannel(INTELLICHEM_TANK2, pic.tank2); + break; + case INTELLICHEM_CALCIUMHARDNESS: + updateChannel(INTELLICHEM_CALCIUMHARDNESS, pic.calciumHardness); + break; + case INTELLICHEM_CYAREADING: + updateChannel(INTELLICHEM_CYAREADING, pic.cyaReading); + break; + case INTELLICHEM_TOTALALKALINITY: + updateChannel(INTELLICHEM_TOTALALKALINITY, pic.totalAlkalinity); + break; + case INTELLICHEM_WATERFLOWALARM: + updateChannel(INTELLICHEM_WATERFLOWALARM, pic.waterFlowAlarm); + break; + case INTELLICHEM_MODE1: + updateChannel(INTELLICHEM_MODE1, pic.mode1); + break; + case INTELLICHEM_MODE2: + updateChannel(INTELLICHEM_MODE2, pic.mode2); + break; + case INTELLICHEM_SATURATIONINDEX: + updateChannel(INTELLICHEM_SATURATIONINDEX, pic.saturationIndex); + break; + } + } + + @Override + public void processPacketFrom(PentairPacket p) { + if (waitStatusForOnline) { + finishOnline(); + } + + switch (p.getAction()) { + case 0x12: // A5 10 09 10 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 + + pic.parsePacket(p); + logger.debug("Intellichem status: {}: ", pic.toString()); + + updateChannel(INTELLICHEM_PHREADING, pic.phReading); + updateChannel(INTELLICHEM_ORPREADING, pic.orpReading); + updateChannel(INTELLICHEM_PHSETPOINT, pic.phSetPoint); + updateChannel(INTELLICHEM_ORPSETPOINT, pic.orpSetPoint); + updateChannel(INTELLICHEM_TANK1, pic.tank1); + updateChannel(INTELLICHEM_TANK2, pic.tank2); + updateChannel(INTELLICHEM_CALCIUMHARDNESS, pic.calciumHardness); + updateChannel(INTELLICHEM_CYAREADING, pic.cyaReading); + updateChannel(INTELLICHEM_TOTALALKALINITY, pic.totalAlkalinity); + updateChannel(INTELLICHEM_WATERFLOWALARM, pic.waterFlowAlarm); + updateChannel(INTELLICHEM_MODE1, pic.mode1); + updateChannel(INTELLICHEM_MODE2, pic.mode2); + updateChannel(INTELLICHEM_SATURATIONINDEX, pic.saturationIndex); + + break; + + default: + logger.debug("Unhandled Intellichem packet: {}", p.toString()); + break; + } + } +} diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChlorHandler.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChlorHandler.java index 6464698f42c0f..73b8df72f5236 100644 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChlorHandler.java +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChlorHandler.java @@ -14,13 +14,16 @@ import static org.openhab.binding.pentair.internal.PentairBindingConstants.*; -import org.openhab.binding.pentair.internal.PentairBindingConstants; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pentair.internal.PentairIntelliChlorPacket; import org.openhab.binding.pentair.internal.PentairPacket; -import org.openhab.binding.pentair.internal.PentairPacketIntellichlor; -import org.openhab.core.library.types.DecimalType; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.slf4j.Logger; @@ -34,91 +37,156 @@ * * @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(); + protected int version; + protected String name = ""; + + /** 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; + + public boolean ok; + public boolean lowFlow; + public boolean lowSalt; + public boolean veryLowSalt; + public boolean highCurrent; + public boolean cleanCell; + public boolean lowVoltage; + public boolean lowWaterTemp; + public boolean commError; + + public static @Nullable PentairIntelliChlorHandler onlineChlorinator; public PentairIntelliChlorHandler(Thing thing) { super(thing); } @Override - public void initialize() { - logger.debug("Initializing IntelliChlor - Thing ID: {}.", this.getThing().getUID()); - - id = 0; // Intellichlor doesn't have ID + public void readConfiguration() { + // IntelliChlor has no configuration parameters, however we need to set id to 0 + this.id = 0; + } - updateStatus(ThingStatus.ONLINE); + @Override + public void goOnline() { + if (onlineChlorinator != null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Another IntelliChlor is already configured."); + return; + } else { + onlineChlorinator = this; + super.goOnline(); + } } @Override - public void dispose() { - logger.debug("Thing {} disposed.", getThing().getUID()); + public void goOffline(ThingStatusDetail detail) { + super.goOffline(detail); + onlineChlorinator = null; } @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 INTELLICHLOR_SALTOUTPUT: + updateChannel(INTELLICHLOR_SALTOUTPUT, saltOutput); + break; + case INTELLICHLOR_SALINITY: + updateChannel(INTELLICHLOR_SALINITY, salinity); + break; + case INTELLICHLOR_OK: + updateChannel(INTELLICHLOR_OK, ok); + break; + case INTELLICHLOR_LOWFLOW: + updateChannel(INTELLICHLOR_LOWFLOW, lowFlow); + break; + case INTELLICHLOR_LOWSALT: + updateChannel(INTELLICHLOR_LOWSALT, lowSalt); + break; + case INTELLICHLOR_VERYLOWSALT: + updateChannel(INTELLICHLOR_VERYLOWSALT, veryLowSalt); + break; + case INTELLICHLOR_HIGHCURRENT: + updateChannel(INTELLICHLOR_HIGHCURRENT, highCurrent); + break; + case INTELLICHLOR_CLEANCELL: + updateChannel(INTELLICHLOR_CLEANCELL, cleanCell); + break; + case INTELLICHLOR_LOWVOLTAGE: + updateChannel(INTELLICHLOR_LOWVOLTAGE, lowVoltage); + break; + case INTELLICHLOR_LOWWATERTEMP: + updateChannel(INTELLICHLOR_LOWWATERTEMP, lowWaterTemp); + break; + case INTELLICHLOR_COMMERROR: + updateChannel(INTELLICHLOR_COMMERROR, commError); + break; + } } } @Override public void processPacketFrom(PentairPacket p) { - PentairPacketIntellichlor pic = (PentairPacketIntellichlor) p; + if (waitStatusForOnline) { // Only go online after first response from the Intellichlor + finishOnline(); + } - switch (pic.getLength()) { - case 3: - if (pic.getCmd() != 0x11) { // only packets with 0x11 have valid saltoutput numbers. - break; - } + PentairIntelliChlorPacket pic = (PentairIntelliChlorPacket) p; - PentairPacketIntellichlor pic3Old = pic3cur; - pic3cur = pic; + switch (p.getAction()) { + case 0x03: + version = pic.getVersion(); + name = pic.getName(); - updateChannel(INTELLICHLOR_SALTOUTPUT, pic3Old); + Map editProperties = editProperties(); + editProperties.put(INTELLICHLOR_PROPERTYVERSION, Integer.toString(version)); + editProperties.put(INTELLICHLOR_PROPERTYMODEL, name); + updateProperties(editProperties); + logger.debug("Intellichlor version: {}, {}", version, name); break; - case 4: - if (pic.getCmd() != 0x12) { - break; - } - - PentairPacketIntellichlor pic4Old = pic4cur; - pic4cur = pic; - updateChannel(INTELLICHLOR_SALINITY, pic4Old); - - break; - } - - logger.debug("Intellichlor command: {}", pic); - } - - /** - * Helper function to compare and update channel if needed. The class variables p29_cur and phsp_cur are used to - * determine the appropriate state of the channel. - * - * @param channel name of channel to be updated, corresponds to channel name in {@link PentairBindingConstants} - * @param p Packet representing the former state. If null, no compare is done and state is updated. - */ - public void updateChannel(String channel, PentairPacket p) { - PentairPacketIntellichlor pic = (PentairPacketIntellichlor) p; - - switch (channel) { - case INTELLICHLOR_SALINITY: - if (pic == null || (pic.salinity != pic4cur.salinity)) { - updateState(channel, new DecimalType(pic4cur.salinity)); - } + case 0x11: // set salt output % command + saltOutput = pic.getSaltOutput(); + updateChannel(INTELLICHLOR_SALTOUTPUT, saltOutput); + logger.debug("Intellichlor set output % {}", saltOutput); break; - case INTELLICHLOR_SALTOUTPUT: - if (pic == null || (pic.saltoutput != pic3cur.saltoutput)) { - updateState(channel, new DecimalType(pic3cur.saltoutput)); + case 0x12: // response to set salt output + salinity = pic.getSalinity(); + + ok = pic.getOk(); + lowFlow = pic.getLowFlow(); + lowSalt = pic.getLowSalt(); + veryLowSalt = pic.getVeryLowSalt(); + highCurrent = pic.getHighCurrent(); + cleanCell = pic.getCleanCell(); + lowVoltage = pic.getLowVoltage(); + lowWaterTemp = pic.getLowWaterTemp(); + + updateChannel(INTELLICHLOR_SALINITY, salinity); + updateChannel(INTELLICHLOR_OK, ok); + updateChannel(INTELLICHLOR_LOWFLOW, lowFlow); + updateChannel(INTELLICHLOR_LOWSALT, lowSalt); + updateChannel(INTELLICHLOR_VERYLOWSALT, veryLowSalt); + updateChannel(INTELLICHLOR_HIGHCURRENT, highCurrent); + updateChannel(INTELLICHLOR_CLEANCELL, cleanCell); + updateChannel(INTELLICHLOR_LOWVOLTAGE, lowVoltage); + updateChannel(INTELLICHLOR_LOWWATERTEMP, lowWaterTemp); + + 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; } } } diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIntelliFloHandler.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIntelliFloHandler.java index 58c2e4eb3f0f9..22a6ce2231e3b 100644 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIntelliFloHandler.java +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairIntelliFloHandler.java @@ -14,74 +14,368 @@ import static org.openhab.binding.pentair.internal.PentairBindingConstants.*; -import java.math.BigDecimal; +import java.util.List; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; -import org.openhab.binding.pentair.internal.PentairBindingConstants; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.pentair.internal.PentairPacket; -import org.openhab.binding.pentair.internal.PentairPacketPumpStatus; +import org.openhab.binding.pentair.internal.PentairPumpStatus; +import org.openhab.binding.pentair.internal.config.PentairIntelliFloHandlerConfig; 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.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; 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 of 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(); + protected PentairPumpStatus pumpStatus = new PentairPumpStatus(); + + // runmode is used to send watchdog to pump when running + private boolean runMode = false; + + /** polling job for pump status */ + protected static @Nullable ScheduledFuture pollingjob; public PentairIntelliFloHandler(Thing thing) { super(thing); } @Override - public void initialize() { - logger.debug("Initializing Intelliflo - Thing ID: {}.", this.getThing().getUID()); + public void readConfiguration() { + PentairIntelliFloHandlerConfig config = getConfigAs(PentairIntelliFloHandlerConfig.class); - id = ((BigDecimal) getConfig().get("id")).intValue(); + this.id = config.id; + } - updateStatus(ThingStatus.ONLINE); + @Override + public void finishOnline() { + super.finishOnline(); + if (pollingjob == null) { + pollingjob = scheduler.scheduleWithFixedDelay(new PumpStatus(), 10, 30, TimeUnit.SECONDS); + } } @Override - public void dispose() { - logger.debug("Thing {} disposed.", getThing().getUID()); + public void goOffline(ThingStatusDetail detail) { + super.goOffline(detail); + + if (pollingjob != null) { + pollingjob.cancel(true); + } + 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. + * + * @author Jeff James + * + */ + class PumpStatus implements Runnable { + @Override + public void run() { + Bridge bridge = getBridge(); + if (bridge == null) { + return; + } + + List things = bridge.getThings(); + + for (Thing t : things) { + if (!t.getThingTypeUID().equals(INTELLIFLO_THING_TYPE)) { + continue; + } + + PentairIntelliFloHandler handler = (PentairIntelliFloHandler) t.getHandler(); + if (handler == null) { + return; + } + + logger.debug("pump runmode = {}", runMode); + + if (handler.runMode) { + logger.debug("Sending watchdog to pump"); + handler.sendPumpOnOROff(true); + } else { + handler.requestPumpStatus(); + } + } + } + }; + + // checkOtherMaster - check to make sure the system does not have a controller OR that the controller is in + // servicemode + protected boolean checkOtherMaster() { + PentairControllerHandler pch = PentairControllerHandler.onlineController; + + if (pch != null && !pch.serviceMode) { + return true; + } + + return false; + } + + /* Commands to send to IntelliFlo */ + + public void sendRequestPumpStatus() { + logger.debug("sendRequestPumpStatus"); + byte[] packet = { (byte) 0xA5, (byte) 0x00, (byte) id, (byte) 0x00 /* source */, (byte) 0x07, (byte) 0x00 }; + + if (!writePacket(packet, 0x07, 1)) { + logger.debug("sendRequestStatus: Timeout"); + } + } + + public void requestPumpStatus() { + logger.debug("requestPumpStatus"); + + sendLocalORRemoteControl(false); + sendRequestPumpStatus(); + } + + public void sendLocalORRemoteControl(boolean bLocal) { + byte[] packet = { (byte) 0xA5, (byte) 0x00, (byte) id, (byte) 0x00 /* source */, (byte) 0x04, (byte) 0x01, + (bLocal) ? (byte) 0x00 : (byte) 0xFF }; + + logger.debug("sendLocalORRemoteControl: {}", bLocal); + + if (!writePacket(packet, 0x04, 1)) { + logger.debug("sendLocalOrRemoteControl: Timeout"); + } + } + + public void sendPumpOnOROff(boolean bOn) { + byte[] packet = { (byte) 0xA5, (byte) 0x00, (byte) id, (byte) 0x00 /* source */, (byte) 0x06, (byte) 0x01, + (bOn) ? (byte) 0x0A : (byte) 0x04 }; + + logger.debug("sendPumpOnOROff: {}", bOn); + if (checkOtherMaster()) { + logger.debug("Unable to send command to pump as there is another master in the system"); + return; + } + + if (!writePacket(packet, 0x06, 1)) { + logger.trace("sendPumpOnOROff: Timeout"); + } + } + + public void setPumpOnOROff(boolean bOn) { + logger.debug("setPumpOnOROff: {}", bOn); + + if (!bOn) { + helperClearPrograms(0); + } + + runMode = bOn; + + sendLocalORRemoteControl(false); + sendPumpOnOROff(bOn); + sendRequestPumpStatus(); + sendLocalORRemoteControl(true); + } + + // sendPumpRPM - low-level call to send to pump the RPM command + public void sendPumpRPM(int rpm) { + int rpmH, rpmL; + + logger.debug("sendPumpRPM: {}", rpm); + if (checkOtherMaster()) { + logger.debug("Unable to send command to pump as there is another master in the system"); + return; + } + + rpmH = rpm / 256; + rpmL = rpm % 256; + + byte[] packet = { (byte) 0xA5, (byte) 0x00, (byte) id, (byte) 0x00 /* source */, (byte) 0x01, (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); + } + + if (!writePacket(packet, 0x01, 1)) { + logger.debug("sendPumpRPM: timeout"); + } + } + + // setPumpRPM - high-level call that includes wrapper commands and delay functions + public void setPumpRPM(int rpm) { + logger.debug("setPumpRPM: {}", rpm); + + helperClearPrograms(0); + + runMode = true; + + sendLocalORRemoteControl(false); + sendPumpRPM(rpm); + sendPumpOnOROff(true); + sendRequestPumpStatus(); + sendLocalORRemoteControl(true); + } + + // sendRunProgram - low-level call to send the command to pump + public void sendRunProgram(int program) { + logger.debug("sendRunProgram: {}", program); + + if (checkOtherMaster()) { + logger.debug("Unable to send command to pump as there is another master in the system"); + return; + } + + if (program < 1 || program > 4) { + return; + } + + byte[] packet = { (byte) 0xA5, (byte) 0x00, (byte) id, (byte) 0x00 /* source */, (byte) 0x01, (byte) 0x04, + (byte) 0x03, (byte) 0x21, (byte) 0x00, (byte) (program << 3) }; + + if (!writePacket(packet, 0x06, 1)) { + logger.debug("sendRunProgram: Timeout"); + } + } + + // setRunProgram - high-level call to run program - including wrapper calls + public void setRunProgram(int program) { + logger.debug("setRunProgram: {}", program); + + helperClearPrograms(program); + + runMode = true; + + sendLocalORRemoteControl(false); + sendRunProgram(program); + sendPumpOnOROff(true); + sendRequestPumpStatus(); + sendLocalORRemoteControl(true); + } + + // helperClearPrograms - turns off any other channels/items that were used to start the pump + public void helperClearPrograms(int program) { + logger.debug("helperClearProgram = {}", program); + + if (program != 1) { + logger.debug("Turn off program1"); + updateState(INTELLIFLO_PROGRAM1, OnOffType.OFF); + } else { + updateState(INTELLIFLO_PROGRAM1, OnOffType.ON); + } + + if (program != 2) { + logger.debug("Turn off program2"); + updateState(INTELLIFLO_PROGRAM2, OnOffType.OFF); + } else { + updateState(INTELLIFLO_PROGRAM2, OnOffType.ON); + } + + if (program != 3) { + logger.debug("Turn off program3"); + updateState(INTELLIFLO_PROGRAM3, OnOffType.OFF); + } else { + updateState(INTELLIFLO_PROGRAM3, OnOffType.ON); + } + + if (program != 4) { + logger.debug("Turn off program4"); + updateState(INTELLIFLO_PROGRAM4, OnOffType.OFF); + } else { + updateState(INTELLIFLO_PROGRAM4, OnOffType.ON); + } } @Override public void handleCommand(ChannelUID channelUID, Command command) { - if (command instanceof RefreshType) { - logger.debug("Intellflo received refresh command"); - updateChannel(channelUID.getId(), null); + logger.debug("handleCommand, {}, {}", channelUID, command); + if (command instanceof OnOffType) { + boolean state = ((OnOffType) command) == OnOffType.ON; + + switch (channelUID.getId()) { + case INTELLIFLO_RUN: + case INTELLIFLO_RPM: + setPumpOnOROff(state); + break; + case INTELLIFLO_PROGRAM1: + if (state) { + setRunProgram(1); + } else { + setPumpOnOROff(false); + } + break; + case INTELLIFLO_PROGRAM2: + if (state) { + setRunProgram(2); + } else { + setPumpOnOROff(false); + } + break; + case INTELLIFLO_PROGRAM3: + if (state) { + setRunProgram(3); + } else { + setPumpOnOROff(false); + } + break; + case INTELLIFLO_PROGRAM4: + if (state) { + setRunProgram(4); + } else { + setPumpOnOROff(false); + } + break; + } + } else if (command instanceof DecimalType) { + int num = ((DecimalType) command).intValue(); + + switch (channelUID.getId()) { + case INTELLIFLO_RPM: + setPumpRPM(num); + break; + } } } @Override public void processPacketFrom(PentairPacket p) { + if (waitStatusForOnline) { + finishOnline(); + } + switch (p.getAction()) { case 1: // Pump command - A5 00 10 60 01 02 00 20 - logger.trace("Pump command (ack): {}: ", p); + logger.debug("Pump command (ack): {}: ", p.toString()); break; case 4: // Pump control panel on/off - logger.trace("Turn pump control panel (ack) {}: {} - {}", p.getSource(), - p.getByte(PentairPacket.STARTOFDATA), p); + boolean remotemode; + + remotemode = p.getByte(0) == (byte) 0xFF; + logger.debug("Pump control panel (ack) {}: {} - {}", p.getSource(), remotemode, p); + break; - case 5: // Set pump mode - logger.trace("Set pump mode (ack) {}: {} - {}", p.getSource(), p.getByte(PentairPacket.STARTOFDATA), p); + case 5: // Set pump mode ack + logger.debug("Set pump mode (ack) {}: {} - {}", p.getSource(), p.getByte(0), p); break; - case 6: // Set run mode - logger.trace("Set run mode (ack) {}: {} - {}", p.getSource(), p.getByte(PentairPacket.STARTOFDATA), p); + case 6: // Set run mode ack + logger.debug("Set run mode (ack) {}: {} - {}", p.getSource(), p.getByte(0), p); break; case 7: // Pump status (after a request) if (p.getLength() != 15) { @@ -89,41 +383,16 @@ public void processPacketFrom(PentairPacket 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: {}", pumpStatus.toString()); + + updateChannel(INTELLIFLO_RUN, pumpStatus.run); + updateChannelPower(INTELLIFLO_POWER, pumpStatus.power); + updateChannel(INTELLIFLO_RPM, pumpStatus.rpm); + updateChannel(INTELLIFLO_GPM, pumpStatus.gpm); + updateChannel(INTELLIFLO_STATUS1, pumpStatus.status1); + updateChannel(INTELLIFLO_STATUS2, pumpStatus.status2); break; default: @@ -133,57 +402,25 @@ public void processPacketFrom(PentairPacket p) { } /** - * 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. + * Helper function to update channel. */ - public void updateChannel(String channel, PentairPacket p) { - // Only called from this class's processPacketFrom, so we are confident this will be a PentairPacketPumpStatus - PentairPacketPumpStatus pps = (PentairPacketPumpStatus) p; - - switch (channel) { - case INTELLIFLO_RUN: - if (pps == null || (pps.run != ppscur.run)) { - updateState(channel, (ppscur.run) ? OnOffType.ON : OnOffType.OFF); - } - break; - case INTELLIFLO_MODE: - if (pps == null || (pps.mode != ppscur.mode)) { - updateState(channel, new DecimalType(ppscur.mode)); - } - break; - case INTELLIFLO_DRIVESTATE: - if (pps == null || (pps.drivestate != ppscur.drivestate)) { - updateState(channel, new DecimalType(ppscur.drivestate)); - } - break; - case INTELLIFLO_POWER: - if (pps == null || (pps.power != ppscur.power)) { - updateState(channel, new DecimalType(ppscur.power)); - } - break; - case INTELLIFLO_RPM: - if (pps == null || (pps.rpm != ppscur.rpm)) { - updateState(channel, new DecimalType(ppscur.rpm)); - } - break; - case INTELLIFLO_PPC: - if (pps == null || (pps.ppc != ppscur.ppc)) { - updateState(channel, new DecimalType(ppscur.ppc)); - } - break; - case INTELLIFLO_ERROR: - if (pps == null || (pps.error != ppscur.error)) { - updateState(channel, new DecimalType(ppscur.error)); - } - break; - case INTELLIFLO_TIMER: - if (pps == null || (pps.timer != ppscur.timer)) { - updateState(channel, new DecimalType(ppscur.timer)); - } - break; - } + @Override + public void updateChannel(String channel, boolean value) { + updateState(channel, (value) ? OnOffType.ON : OnOffType.OFF); + } + + @Override + public void updateChannel(String channel, int value) { + updateState(channel, new DecimalType(value)); + } + + @Override + public void updateChannel(String channel, String value) { + updateState(channel, new StringType(value)); + } + + @Override + public void updateChannelPower(String channel, int value) { + updateState(channel, new QuantityType<>(value, Units.WATT)); } } diff --git a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairSerialBridgeHandler.java b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairSerialBridgeHandler.java index 30958d0b9cec9..f6eb08213dd9b 100644 --- a/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairSerialBridgeHandler.java +++ b/bundles/org.openhab.binding.pentair/src/main/java/org/openhab/binding/pentair/internal/handler/PentairSerialBridgeHandler.java @@ -12,133 +12,125 @@ */ 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.util.Objects; +import java.util.Optional; +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} * * @author Jeff James - initial contribution * */ +@NonNullByDefault public class PentairSerialBridgeHandler extends PentairBaseBridgeHandler { private final Logger logger = LoggerFactory.getLogger(PentairSerialBridgeHandler.class); + public PentairSerialBridgeConfig config = new PentairSerialBridgeConfig(); /** SerialPort object representing the port where the RS485 adapter is connected */ - SerialPort port; + private final SerialPortManager serialPortManager; + private Optional port = Optional.empty(); + private @Nullable SerialPortIdentifier portIdentifier; - public PentairSerialBridgeHandler(Bridge bridge) { + public PentairSerialBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) { super(bridge); + this.serialPortManager = serialPortManager; } @Override - protected synchronized void connect() { - PentairSerialBridgeConfig configuration = getConfigAs(PentairSerialBridgeConfig.class); + protected synchronized int connect() { + config = getConfigAs(PentairSerialBridgeConfig.class); + + if (config.serialPort.isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no serial port configured"); + return -1; + } + + this.id = config.id; + logger.debug("Serial port id: {}", id); + this.discovery = config.discovery; + + portIdentifier = serialPortManager.getIdentifier(config.serialPort); + if (portIdentifier == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Configured serial port does not exist"); + return -1; + } try { - CommPortIdentifier ci = CommPortIdentifier.getPortIdentifier(configuration.serialPort); - CommPort cp = ci.open("openhabpentairbridge", 10000); - if (cp == null) { - throw new IllegalStateException("cannot open serial port!"); + logger.debug("connect port: {}", config.serialPort); + + Objects.requireNonNull(portIdentifier, "portIdentifier is null"); + if (portIdentifier.isCurrentlyOwned()) { + logger.debug("Serial port is currently being used by another application {}", + portIdentifier.getCurrentOwner()); + // for debug purposes, will continue to try and open } - if (cp instanceof SerialPort) { - port = (SerialPort) cp; - } else { - throw new IllegalStateException("unknown port type"); + port = Optional.of(portIdentifier.open("org.openhab.binding.pentair", 10000)); + + if (!port.isPresent()) { + return -1; + } + + port.get().setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); + port.get().setFlowControlMode(SerialPort.FLOWCONTROL_NONE); + + InputStream is = port.get().getInputStream(); + OutputStream os = port.get().getOutputStream(); + + if (is != null) { + setInputStream(is); } - port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); - port.disableReceiveFraming(); - port.disableReceiveThreshold(); - reader = new BufferedInputStream(port.getInputStream()); - writer = new BufferedOutputStream(port.getOutputStream()); - logger.info("Pentair Bridge connected to serial port: {}", configuration.serialPort); + if (os != null) { + setOutputStream(os); + } } catch (PortInUseException e) { - String msg = String.format("cannot open serial port: %s", configuration.serialPort); + String msg = String.format("Serial port already in use: %s", config.serialPort); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg); - return; + return -1; } catch (UnsupportedCommOperationException e) { - String msg = String.format("got unsupported operation %s on port %s", e.getMessage(), - configuration.serialPort); + String msg = String.format("got unsupported operation %s on port %s", e.getMessage(), config.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; + return -2; } catch (IOException e) { - String msg = String.format("IOException on port %s", configuration.serialPort); + String msg = String.format("got IOException %s on port %s", e.getMessage(), config.serialPort); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg); - return; + return -2; } - parser = new Parser(); - thread = new Thread(parser); - thread.start(); + // if you have gotten this far, you should be connected to the serial port + 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"); - } + updateStatus(ThingStatus.ONLINE); + + return 0; } @Override protected synchronized void disconnect() { updateStatus(ThingStatus.OFFLINE); - if (thread != null) { - try { - thread.interrupt(); - thread.join(); // wait for thread to complete - } catch (InterruptedException e) { - // do nothing - } - thread = null; - parser = null; - } - - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - logger.trace("IOException when closing serial reader", e); - } - reader = null; - } - - if (writer != null) { - try { - writer.close(); - } catch (IOException e) { - logger.trace("IOException when closing serial writer", e); - } - writer = null; - } - - if (port != null) { - port.close(); - port = null; + if (port.isPresent()) { + port.get().close(); + port = Optional.empty(); } } } diff --git a/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/controller.xml b/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/controller.xml new file mode 100644 index 0000000000000..fd15644d46d2c --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/controller.xml @@ -0,0 +1,375 @@ + + + + + + + + + + Pentair Controller + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FWVersion + + + id + + + + + The ID of the device (in decimal, not hex) + 16 + + + + + Enables automatic synchronization of the pool controller clock with the system clock + true + + + + + + + + + Circuit + + + + + + + + + + Features + + + + + + + + + + Heat + + + + + + + + + + schedule + + + + + + + + + + + + + General status channels for controller + + + + + + + + + + + + + + + Number:Temperature + + Water temperature. Only valid when pool pump is running. + + + + + Number:Temperature + + Solar temperature. + + + + + Number:Temperature + + Air temperature. + + + + + Switch + + Auxillary Switch + + + + String + + Name of circuit + + + + + String + + Function of circuit + + + + + String + + Heat mode + + + + + + + + + + + + + Number:Temperature + + Temperature set point + + + + + String + + Light mode + + + + + + + + + + + + + + + + + + + + + recommend + + + + String + + >Type of schedule (None, Normal, EggTimer, OnceOnly) + + + + + + + + + + + + String + + String format of schedule + + + + Number + + Start Time in minutes + + + + + Number + + End time (or duration for Egg Timer) of schedule in minutes + + + + + Number + + >Circuit + + + + String + + >Days + + + + String + + Unit of Measure + + + + + + + + + + Switch + + Controller is in service mode + + + + + Switch + + Pump is continuing to run to allow the heater to cool. + + + + Switch + + Solar heater is on + + + + + Switch + + Heater is on + + + + diff --git a/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/easytouch.xml b/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/easytouch.xml deleted file mode 100644 index 37db93dac7412..0000000000000 --- a/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/easytouch.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - - - Pentair EasyTouch Controller - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The ID of the device (in decimal, not hex) - 16 - - - - - - Number - - Pool water temperature. Only valid when pool pump is running and in pool mode. - - - - Number - - Spa water temperature. Only valide when in spa mode. - - - - Number - - Air temperature. - - - - Number - - Solar temperature. - - - - Switch - - Auxillary Switch - - - - Switch - - Feature Switch - - - - Number - - Heat mode - - - - - - - - - - - - Number - - Heat active state - - - - - Number - - Pool temperature set point - - - - Number - - Spa temperature set point - - - - String - - Heat mode string - - - - diff --git a/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/intellichem.xml b/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/intellichem.xml new file mode 100644 index 0000000000000..8e3498250c70e --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/intellichem.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + Pentair Intellichem + + + + + + + + + + + + + + + + + + id + + + + + The ID of the device (in decimal, not hex) + 144 + + + + + + Number + + Current PH reading. + + + + + Number + + Current Oxidation Reduction Potential (ORP) reading. + + + + + Number + + Current PH set point. + + + + + Number + + Oxidation Reduction Potential (ORP) set point. + + + + + Number + + Tank level. + + + + + Number:Dimensionless + + Calcium hardness (ppm). + + + + + Number:Dimensionless + + Cyanuric acid reading (ppm). + + + + + Number:Dimensionless + + Total Alkalinity reading (ppm). + + + + + Switch + + Water flow alarm (on = no water flow). + + + + + Number + + Intellichem mode to denote wither it is dosing or mixing. Not reverse engineered. + + + + + Number + + Saturation Index. + + + + diff --git a/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/intellichlor.xml b/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/intellichlor.xml index a5f133af04b96..ecb91a0b2aea9 100644 --- a/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/intellichlor.xml +++ b/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/intellichlor.xml @@ -7,38 +7,101 @@ - + Pentair Intellichlor IC40 - - + + + + + + + + + + - - - - The ID of the device (in decimal, not hex) - 96 - - + + Version + Model + + + id - + Number Current salt output setting for the chlorinator (%). - + Number Current salt content reading of the water (PPM). + + Switch + + Chlorinator is operating correctly. + + + + + Switch + + Water flow rate is low. + + + + + Switch + + Low salt level. + + + + + Switch + + Very low salt level. + + + + + Switch + + Chlorinator drawing high current. + + + + + Switch + + Clean chlorinator cell. + + + + + Switch + + Low voltage. + + + + + Switch + + Water temperature is too low for chlorine generation. + + + diff --git a/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/intelliflo.xml b/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/intelliflo.xml index f8968beeb6eba..abfec3fcd09f9 100644 --- a/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/intelliflo.xml +++ b/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/intelliflo.xml @@ -7,23 +7,29 @@ - + - + Pentair Intelliflo Pump - - - - - + + + + + + + + + + id + - + The ID of the device (in decimal, not hex) 96 @@ -31,13 +37,6 @@ - - Number - - Pump mode - - - Switch @@ -45,32 +44,45 @@ - + Number Pump RPM - + - + Number + + Pump GPM (only valid for VF pumps) + + + + + Number:Power Pump power - + - + Number - - Pump PPC + + Pump Status 1 - + Number - - Pump Error + + Pump Status 2 + + Switch + + Pump program switch + + diff --git a/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/ip_bridge.xml b/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/ip_bridge.xml index 5881cc471a311..5e1d0f38334dc 100644 --- a/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/ip_bridge.xml +++ b/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/ip_bridge.xml @@ -12,6 +12,7 @@ The IP address to connect to. network-address + 127.0.0.1 @@ -23,7 +24,13 @@ The ID to use to send commands on the Pentair bus (default: 34) - 34 + 33 + + + + + Enable automatic discovery of devices + true diff --git a/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/serial_bridge.xml b/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/serial_bridge.xml index 40b04c7f553f7..5d8dcf910a088 100644 --- a/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/serial_bridge.xml +++ b/bundles/org.openhab.binding.pentair/src/main/resources/OH-INF/thing/serial_bridge.xml @@ -4,8 +4,8 @@ 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"> - - + + This bridge is used when using a USB->RS485 interface. @@ -18,6 +18,12 @@ The ID to use to send commands on the Pentair bus (default: 34) 34 + + + + Enable automatic discovery of devices + true + diff --git a/bundles/org.openhab.binding.pentair/src/test/data/easytouch8.dat b/bundles/org.openhab.binding.pentair/src/test/data/easytouch8.dat new file mode 100644 index 0000000000000..c0084d92fb80a --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/data/easytouch8.dat @@ -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 \ No newline at end of file diff --git a/bundles/org.openhab.binding.pentair/src/test/data/easytouch8b.dat b/bundles/org.openhab.binding.pentair/src/test/data/easytouch8b.dat new file mode 100644 index 0000000000000..21dc3bac3c91f --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/data/easytouch8b.dat @@ -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 diff --git a/bundles/org.openhab.binding.pentair/src/test/data/nodejs-capture.dat b/bundles/org.openhab.binding.pentair/src/test/data/nodejs-capture.dat new file mode 100644 index 0000000000000..051815745f9e0 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/data/nodejs-capture.dat @@ -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 diff --git a/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairControllerScheduleTest.java b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairControllerScheduleTest.java new file mode 100644 index 0000000000000..301dcaed67983 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairControllerScheduleTest.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +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; + +/** + * PentairControllerSchduleTest + * + * @author Jeff James - Initial contribution + * + */ +@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(); + + PentairPacket p = new PentairPacket(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)); + + PentairPacket p2 = new PentairPacket(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)); + + PentairPacket p3 = new PentairPacket(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)); + + PentairPacket p4 = new PentairPacket(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; + + PentairPacket p = pcs.getWritePacket(0x10, 0x00); + + assertThat(p.buf, is(parsehex("A5 00 10 00 91 07 01 04 05 0F 0A 1E 07"))); + } +} diff --git a/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairControllerStatusTest.java b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairControllerStatusTest.java new file mode 100644 index 0000000000000..9633c6a4b5415 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairControllerStatusTest.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import static org.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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PentairControllerStatusTest} + * + * @author Jeff James - Initial contribution + */ +@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(); + + PentairPacket p = new PentairPacket(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 PentairPacket(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 PentairPacket(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 PentairPacket(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)); + } +} diff --git a/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairHeatStatusTest.java b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairHeatStatusTest.java new file mode 100644 index 0000000000000..121344b0c03c5 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairHeatStatusTest.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.pentair.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.openhab.binding.pentair.internal.TestUtilities.parsehex; + +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PentairHeatStatusTest} + * + * @author Jeff James - Initial contribution + */ + +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(); + + PentairPacket p = new PentairPacket(packets[0], packets[0].length); + hs.parsePacket(p); + + logger.info(hs.toString()); + + assertThat(hs.poolSetPoint, equalTo(85)); + assertThat(hs.poolHeatMode, equalTo(PentairHeatStatus.HeatMode.SOLAR)); + assertThat(hs.spaSetPoint, equalTo(94)); + assertThat(hs.spaHeatMode, equalTo(PentairHeatStatus.HeatMode.HEATER)); + } +} diff --git a/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairIntelliChemTest.java b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairIntelliChemTest.java new file mode 100644 index 0000000000000..71b8441b0eadf --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairIntelliChemTest.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import static org.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; + +/** + * PentairIntelliChemTest + * + * @author Jeff James - Initial contribution + * + */ +@NonNullByDefault +public class PentairIntelliChemTest { + + //@formatter:off + public static byte[][] packets = { + parsehex("A50010901229030202A302D002C60000000000000000000000000006070000C8003F005A3C00580006A5201E01000000"), + parsehex("A5100F10122902E302AF02EE02BC000000020000002A0004005C060518019000000096140051000065203C0100000000") + }; + //@formatter:on + + @Test + public void test() { + PentairIntelliChem pic = new PentairIntelliChem(); + PentairPacket p1 = new PentairPacket(packets[0], packets[0].length); + + pic.parsePacket(p1); + + assertThat(pic.phReading, equalTo(7.70)); + assertThat(pic.orpReading, equalTo(675)); + assertThat(pic.phSetPoint, equalTo(7.20)); + assertThat(pic.orpSetPoint, equalTo(710)); + assertThat(pic.tank1, equalTo(0)); + assertThat(pic.tank2, equalTo(6)); + // assertThat(pic.calciumhardness, equalTo(0)); + assertThat(pic.cyaReading, equalTo(63)); + assertThat(pic.totalAlkalinity, equalTo(0)); + assertThat(pic.waterFlowAlarm, equalTo(true)); + assertThat(pic.mode1, equalTo(0x06)); + assertThat(pic.mode2, equalTo(0xA5)); + + assertThat(pic.calcCalciumHardnessFactor(), equalTo(1.0)); + + PentairPacket p2 = new PentairPacket(packets[1], packets[1].length); + pic.parsePacket(p2); + + assertThat(pic.phReading, equalTo(7.39)); + assertThat(pic.orpReading, equalTo(687)); + assertThat(pic.phSetPoint, equalTo(7.50)); + assertThat(pic.orpSetPoint, equalTo(700)); + assertThat(pic.tank1, equalTo(6)); + assertThat(pic.tank2, equalTo(5)); + // assertThat(pic.calciumhardness, equalTo(0)); + assertThat(pic.cyaReading, equalTo(0)); + assertThat(pic.totalAlkalinity, equalTo(150)); + assertThat(pic.waterFlowAlarm, equalTo(false)); + assertThat(pic.mode1, equalTo(0x65)); + assertThat(pic.mode2, equalTo(0x20)); + + assertThat(pic.calcCalciumHardnessFactor(), equalTo(2.2)); + } +} diff --git a/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairParserTest.java b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairParserTest.java new file mode 100644 index 0000000000000..74fe94a77f3fd --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairParserTest.java @@ -0,0 +1,198 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import static org.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.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.openhab.binding.pentair.internal.PentairParser.CallbackPentairParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * PentairParserTest + * + * @author Jeff James - Initial contribution + * + */ +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 + CallbackPentairParser callback; + + @Captor + ArgumentCaptor packets; + + @Captor + ArgumentCaptor packetsIntellichlor; + + Thread thread; + + @BeforeEach + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + parser.setCallback(callback); + } + + @AfterEach + public void tearDown() throws Exception { + if (thread != null) { + thread.interrupt(); + thread.join(); + } + thread = null; + } + + @Test + public void test() throws InterruptedException { + java.lang.System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "DEBUG"); + + logger.debug("debug"); + logger.info("info"); + + 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(packets.capture()); + + List allPackets = new ArrayList(); + allPackets = packets.getAllValues(); + + assertThat(allPackets.size(), equalTo(3)); + + logger.info("1: {}", allPackets.get(0).getAction()); + logger.info("2: {}", allPackets.get(1).getAction()); + logger.info("3: {}", allPackets.get(2).getAction()); + + assertThat(allPackets.get(0).getAction(), equalTo(0x02)); + + assertThat(allPackets.get(1).getAction(), equalTo(0x12)); + + assertThat(allPackets.get(2).getAction(), 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(packets.capture()); + verify(callback, atLeast(1)).onIntelliChlorPacket(packetsIntellichlor.capture()); + + List allPackets = new ArrayList(); + allPackets = packets.getAllValues(); + + logger.info("Number of Pentair packets: {}", allPackets.size()); + + assertThat(allPackets.size(), equalTo(281)); + + List allPacketsIntellichlor = new ArrayList(); + 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(packets.capture()); + verify(callback, atLeast(1)).onIntelliChlorPacket(packetsIntellichlor.capture()); + + List allPackets = new ArrayList(); + allPackets = packets.getAllValues(); + + logger.info("Number of Pentair packets: {}", allPackets.size()); + + assertThat(allPackets.size(), equalTo(1032)); + + List allPacketsIntellichlor = new ArrayList(); + allPacketsIntellichlor = packetsIntellichlor.getAllValues(); + + logger.info("Number of Intellichlor packets: {}", allPacketsIntellichlor.size()); + + assertThat(allPacketsIntellichlor.size(), equalTo(36)); + } +} diff --git a/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairPumpStatusTest.java b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairPumpStatusTest.java new file mode 100644 index 0000000000000..9b5c5805c63e1 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/PentairPumpStatusTest.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal; + +import static org.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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PentairPumpStatusTest} + * + * @author Jeff James - Initial contribution + */ +@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(); + + PentairPacket p = new PentairPacket(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 PentairPacket(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 PentairPacket(packets[2], packets[2].length); + ps.parsePacket(p); + logger.debug(ps.toString()); + + p = new PentairPacket(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)); + } +} diff --git a/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/TestUtilities.java b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/TestUtilities.java new file mode 100644 index 0000000000000..e5798b3315bdc --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/TestUtilities.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.pentair.internal; + +import 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; + } +} diff --git a/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairControllerHandlerTest.java b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairControllerHandlerTest.java new file mode 100644 index 0000000000000..82f8eb698a75d --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairControllerHandlerTest.java @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal.handler; + +import static org.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.Collections; + +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.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.openhab.binding.pentair.internal.PentairPacket; +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.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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PentairControllerHandlerTest } + * + * @author Jeff James - Initial contribution + */ +public class PentairControllerHandlerTest { + 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 + + private PentairControllerHandler handler; + + @Mock + private Bridge bridge; + + @Mock + private ThingHandlerCallback callback; + + @Mock + private Thing thing; + + @Mock + private PentairIPBridgeHandler pibh; + + @BeforeAll + public static void setUpBeforeClass() throws Exception { + } + + @AfterAll + public static void tearDownAfterClass() throws Exception { + } + + @BeforeEach + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(bridge.getStatus()).thenReturn(ThingStatus.ONLINE); + when(thing.getConfiguration()).thenReturn(new Configuration(Collections.singletonMap("id", 0x10))); + when(thing.getUID()).thenReturn(new ThingUID("1:2:3")); + + pibh = new PentairIPBridgeHandler(bridge); + + handler = new PentairControllerHandler(thing) { + @Override + public PentairBaseBridgeHandler getBridgeHandler() { + return pibh; + } + }; + + handler.setCallback(callback); + } + + @AfterEach + public void tearDown() throws Exception { + handler.dispose(); + } + + @Test + public void testPacketProcessing() { + handler.initialize(); + + verify(callback, times(1)).statusUpdated(eq(thing), + argThat(arg -> arg.getStatus().equals(ThingStatus.UNKNOWN))); + + PentairPacket p = new PentairPacket(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(new ThingUID("1:2:3"), CONTROLLER_SPACIRCUIT + "#switch"); + verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON); + cuid = new ChannelUID(new ThingUID("1:2:3"), CONTROLLER_POOLCIRCUIT + "#switch"); + verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON); + cuid = new ChannelUID(new ThingUID("1:2:3"), CONTROLLER_STATUS + "#" + CONTROLLER_AIRTEMPERATURE); + verify(callback, times(1)).stateUpdated(cuid, new QuantityType<>(65, ImperialUnits.FAHRENHEIT)); + + Mockito.reset(callback); + + p = new PentairPacket(packets[1], packets[1].length); + handler.processPacketFrom(p); + + Mockito.reset(callback); + + p = new PentairPacket(packets[2], packets[2].length); + handler.processPacketFrom(p); + cuid = new ChannelUID(new ThingUID("1:2:3"), CONTROLLER_POOLCIRCUIT + "#switch"); + verify(callback, times(1)).stateUpdated(cuid, OnOffType.OFF); + cuid = new ChannelUID(new ThingUID("1:2:3"), CONTROLLER_FEATURE3 + "#switch"); + verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON); + cuid = new ChannelUID(new ThingUID("1:2:3"), CONTROLLER_FEATURE4 + "#switch"); + verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON); + cuid = new ChannelUID(new ThingUID("1:2:3"), CONTROLLER_STATUS + "#" + CONTROLLER_AIRTEMPERATURE); + verify(callback, times(1)).stateUpdated(cuid, new QuantityType<>(69, ImperialUnits.FAHRENHEIT)); + + p = new PentairPacket(packets[3], packets[3].length); + handler.processPacketFrom(p); + } +} diff --git a/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChemHandlerTest.java b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChemHandlerTest.java new file mode 100644 index 0000000000000..53cc41a738e86 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChemHandlerTest.java @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.pentair.internal.handler; + +import static org.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.Collections; + +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.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.openhab.binding.pentair.internal.PentairPacket; +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.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.ThingUID; +import org.openhab.core.thing.binding.ThingHandlerCallback; + +/** + * PentairIntelliChemHandlerTest + * + * @author Jeff James - Initial contribution + * + */ +public class PentairIntelliChemHandlerTest { + + //@formatter:off + public static byte[][] packets = { + parsehex("A50010901229030202A302D002C60000000000000000000000000006070000C8003F005A3C00580006A5201E01000000"), + parsehex("A5100F10122902E302AF02EE02BC000000020000002A0004005C060518019000000096140051000065203C0100000000") + }; + //@formatter:on + + private PentairIntelliChemHandler pich; + + @Mock + private Bridge bridge; + + @Mock + private ThingHandlerCallback callback; + + @Mock + private Thing thing; + + @Mock + private PentairIPBridgeHandler pibh; + + @BeforeAll + public static void setUpBeforeClass() throws Exception { + } + + @AfterAll + public static void tearDownAfterClass() throws Exception { + } + + @BeforeEach + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(bridge.getStatus()).thenReturn(ThingStatus.ONLINE); + when(thing.getConfiguration()).thenReturn(new Configuration(Collections.singletonMap("id", 144))); + when(thing.getUID()).thenReturn(new ThingUID("1:2:3")); + + pibh = new PentairIPBridgeHandler(bridge); + + pich = new PentairIntelliChemHandler(thing) { + @Override + public PentairBaseBridgeHandler getBridgeHandler() { + return pibh; + } + }; + + pich.setCallback(callback); + } + + @AfterEach + public void tearDown() throws Exception { + pich.dispose(); + } + + @Test + public void test() { + pich.initialize(); + + PentairPacket p = new PentairPacket(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(new ThingUID("1:2:3"), INTELLICHEM_PHREADING); + verify(callback).stateUpdated(cuid, new DecimalType(7.7)); + + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLICHEM_WATERFLOWALARM); + verify(callback).stateUpdated(cuid, OnOffType.ON); + + // TODO: process 2nd packet + } +} diff --git a/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChlorHandlerTest.java b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChlorHandlerTest.java new file mode 100644 index 0000000000000..7b63d07b41c0b --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairIntelliChlorHandlerTest.java @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.pentair.internal.handler; + +import static org.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 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.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.openhab.binding.pentair.internal.PentairIntelliChlorPacket; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +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.ThingUID; +import org.openhab.core.thing.binding.ThingHandlerCallback; + +/** + * PentairIntelliChloreHandlerTest + * + * @author Jeff James - Initial contribution + * + */ +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 PentairIntelliChlorHandler pic_handler; + + @Mock + private Bridge bridge; + + @Mock + private ThingHandlerCallback callback; + + @Mock + private Thing thing; + + @Mock + private PentairIPBridgeHandler pibh; + + @BeforeAll + public static void setUpBeforeClass() throws Exception { + } + + @AfterAll + public static void tearDownAfterClass() throws Exception { + } + + @BeforeEach + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(bridge.getStatus()).thenReturn(ThingStatus.ONLINE); + // when(thing.getConfiguration()).thenReturn(new Configuration(Collections.singletonMap("id", 144))); + when(thing.getUID()).thenReturn(new ThingUID("1:2:3")); + + pibh = new PentairIPBridgeHandler(bridge); + + pic_handler = new PentairIntelliChlorHandler(thing) { + @Override + public PentairBaseBridgeHandler getBridgeHandler() { + return pibh; + } + }; + + pic_handler.setCallback(callback); + } + + @AfterEach + public void tearDown() throws Exception { + pic_handler.dispose(); + } + + @Test + public void test() { + pic_handler.initialize(); + + assertThat(pic_handler.id, equalTo(0)); + + PentairIntelliChlorPacket p = new PentairIntelliChlorPacket(packets[0], packets[0].length); + pic_handler.processPacketFrom(p); + verify(callback, times(1)).statusUpdated(eq(thing), argThat(arg -> arg.getStatus().equals(ThingStatus.ONLINE))); + ChannelUID cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLICHLOR_SALTOUTPUT); + verify(callback, times(1)).stateUpdated(cuid, new DecimalType(80)); + + p = new PentairIntelliChlorPacket(packets[1], packets[1].length); + pic_handler.processPacketFrom(p); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLICHLOR_SALINITY); + verify(callback, times(1)).stateUpdated(cuid, new DecimalType(5150)); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLICHLOR_OK); + verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON); + + p = new PentairIntelliChlorPacket(packets[2], packets[2].length); + pic_handler.processPacketFrom(p); + + p = new PentairIntelliChlorPacket(packets[3], packets[3].length); + pic_handler.processPacketFrom(p); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLICHLOR_SALTOUTPUT); + verify(callback, times(1)).stateUpdated(cuid, new DecimalType(0)); + + p = new PentairIntelliChlorPacket(packets[4], packets[4].length); + pic_handler.processPacketFrom(p); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLICHLOR_SALINITY); + verify(callback, times(1)).stateUpdated(cuid, new DecimalType(3800)); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLICHLOR_OK); + verify(callback, times(1)).stateUpdated(cuid, OnOffType.OFF); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLICHLOR_LOWFLOW); + verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON); + + p = new PentairIntelliChlorPacket(packets[5], packets[5].length); + pic_handler.processPacketFrom(p); + assertThat(pic_handler.version, equalTo(0)); + assertThat(pic_handler.name, equalTo("Intellichlor--40")); + + p = new PentairIntelliChlorPacket(packets[6], packets[6].length); + pic_handler.processPacketFrom(p); + assertThat(pic_handler.version, equalTo(0)); + assertThat(pic_handler.name, equalTo("Intellichlor--40")); + } +} diff --git a/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairIntelliFloHandlerTest.java b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairIntelliFloHandlerTest.java new file mode 100644 index 0000000000000..b7d0ec6ba9773 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/java/org/openhab/binding/pentair/internal/handler/PentairIntelliFloHandlerTest.java @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.pentair.internal.handler; + +import static org.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.Collections; + +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.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.openhab.binding.pentair.internal.PentairPacket; +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.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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PentailIntelliFlowHandlerTest } + * + * @author Jeff James - Initial contribution + */ +public class PentairIntelliFloHandlerTest { + 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 PentairIntelliFloHandler handler; + + @Mock + private Bridge bridge; + + @Mock + private ThingHandlerCallback callback; + + @Mock + private Thing thing; + + @Mock + private PentairIPBridgeHandler pibh; + + @BeforeAll + public static void setUpBeforeClass() throws Exception { + } + + @AfterAll + public static void tearDownAfterClass() throws Exception { + } + + @BeforeEach + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(bridge.getStatus()).thenReturn(ThingStatus.ONLINE); + when(thing.getConfiguration()).thenReturn(new Configuration(Collections.singletonMap("id", 0x10))); + when(thing.getUID()).thenReturn(new ThingUID("1:2:3")); + + pibh = new PentairIPBridgeHandler(bridge); + + handler = new PentairIntelliFloHandler(thing) { + @Override + public PentairBaseBridgeHandler getBridgeHandler() { + return pibh; + } + }; + + handler.setCallback(callback); + } + + @AfterEach + public void tearDown() throws Exception { + handler.dispose(); + } + + @Test + public void testPacketProcessing() { + ChannelUID cuid; + PentairPacket p; + + handler.initialize(); + + p = new PentairPacket(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(new ThingUID("1:2:3"), INTELLIFLO_RUN); + verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLIFLO_POWER); + verify(callback, times(1)).stateUpdated(cuid, new QuantityType<>(231, Units.WATT)); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLIFLO_RPM); + verify(callback, times(1)).stateUpdated(cuid, new DecimalType(1750)); + + Mockito.reset(callback); + + p = new PentairPacket(packets[1], packets[1].length); + handler.processPacketFrom(p); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLIFLO_RUN); + verify(callback, times(1)).stateUpdated(cuid, OnOffType.ON); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLIFLO_POWER); + verify(callback, times(1)).stateUpdated(cuid, new QuantityType<>(505, Units.WATT)); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLIFLO_RPM); + verify(callback, times(1)).stateUpdated(cuid, new DecimalType(2005)); + + Mockito.reset(callback); + + p = new PentairPacket(packets[2], packets[2].length); + handler.processPacketFrom(p); + + Mockito.reset(callback); + + p = new PentairPacket(packets[3], packets[3].length); + handler.processPacketFrom(p); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLIFLO_RUN); + verify(callback, times(1)).stateUpdated(cuid, OnOffType.OFF); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLIFLO_POWER); + verify(callback, times(1)).stateUpdated(cuid, new QuantityType<>(0, Units.WATT)); + cuid = new ChannelUID(new ThingUID("1:2:3"), INTELLIFLO_RPM); + verify(callback, times(1)).stateUpdated(cuid, new DecimalType(0)); + } +} diff --git a/bundles/org.openhab.binding.pentair/src/test/resources/log4j.xml b/bundles/org.openhab.binding.pentair/src/test/resources/log4j.xml new file mode 100644 index 0000000000000..d20f3bf61b20d --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/resources/log4j.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.pentair/src/test/resources/logback-test.xml b/bundles/org.openhab.binding.pentair/src/test/resources/logback-test.xml new file mode 100644 index 0000000000000..3851a3a21d36a --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/resources/logback-test.xml @@ -0,0 +1,45 @@ + + + + + + %d{HH:mm:ss.SSS} [%.15thread] %-5level %logger{36}:%line - %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.pentair/src/test/resources/simplelogger.properties b/bundles/org.openhab.binding.pentair/src/test/resources/simplelogger.properties new file mode 100644 index 0000000000000..ac0f70a707da5 --- /dev/null +++ b/bundles/org.openhab.binding.pentair/src/test/resources/simplelogger.properties @@ -0,0 +1,34 @@ +# SLF4J's SimpleLogger configuration file +# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. + +# Default logging detail level for all instances of SimpleLogger. +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, defaults to "info". +org.slf4j.simpleLogger.defaultLogLevel=debug + +# Logging detail level for a SimpleLogger instance named "xxxxx". +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, the default logging detail level is used. +#org.slf4j.simpleLogger.log.xxxxx= + +# Set to true if you want the current date and time to be included in output messages. +# Default is false, and will output the number of milliseconds elapsed since startup. +#org.slf4j.simpleLogger.showDateTime=false + +# The date and time format to be used in the output messages. +# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. +# If the format is not specified or is invalid, the default format is used. +# The default format is yyyy-MM-dd HH:mm:ss:SSS Z. +#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z + +# Set to true if you want to output the current thread name. +# Defaults to true. +#org.slf4j.simpleLogger.showThreadName=true + +# Set to true if you want the Logger instance name to be included in output messages. +# Defaults to true. +#org.slf4j.simpleLogger.showLogName=true + +# Set to true if you want the last component of the name to be included in output messages. +# Defaults to false. +#org.slf4j.simpleLogger.showShortLogName=false \ No newline at end of file