2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.pentair.internal.handler;
15 import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
17 import java.math.BigDecimal;
18 import java.util.List;
19 import java.util.Objects;
21 import org.openhab.binding.pentair.internal.PentairBindingConstants;
22 import org.openhab.binding.pentair.internal.PentairPacket;
23 import org.openhab.binding.pentair.internal.PentairPacketHeatSetPoint;
24 import org.openhab.binding.pentair.internal.PentairPacketStatus;
25 import org.openhab.core.library.types.DecimalType;
26 import org.openhab.core.library.types.OnOffType;
27 import org.openhab.core.library.types.StringType;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.thing.Thing;
30 import org.openhab.core.thing.ThingStatus;
31 import org.openhab.core.thing.ThingStatusDetail;
32 import org.openhab.core.types.Command;
33 import org.openhab.core.types.RefreshType;
34 import org.openhab.core.types.UnDefType;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * The {@link PentairEasyTouchHandler} is responsible for implementation of the EasyTouch Controller. It will handle
40 * commands sent to a thing and implements the different channels. It also parses/disposes of the packets seen on the
41 * bus from the controller.
43 * @author Jeff James - Initial contribution
45 public class PentairEasyTouchHandler extends PentairBaseThingHandler {
47 private final Logger logger = LoggerFactory.getLogger(PentairEasyTouchHandler.class);
50 * current/last status packet recieved, used to compare new packet values to determine if status needs to be updated
52 protected PentairPacketStatus p29cur = new PentairPacketStatus();
53 /** current/last heat set point packet, used to determine if status in framework should be updated */
54 protected PentairPacketHeatSetPoint phspcur = new PentairPacketHeatSetPoint();
56 public PentairEasyTouchHandler(Thing thing) {
61 public void initialize() {
62 logger.debug("Initializing EasyTouch - Thing ID: {}.", this.getThing().getUID());
64 id = ((BigDecimal) getConfig().get("id")).intValue();
66 // make sure there are no exisitng EasyTouch controllers
67 PentairBaseBridgeHandler bh = (PentairBaseBridgeHandler) this.getBridge().getHandler();
68 List<Thing> things = bh.getThing().getThings();
70 for (Thing t : things) {
71 if (t.getUID().equals(this.getThing().getUID())) {
74 if (t.getThingTypeUID().equals(EASYTOUCH_THING_TYPE)) {
75 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
76 "Another EasyTouch controller is already configured.");
81 updateStatus(ThingStatus.ONLINE);
85 public void dispose() {
86 logger.debug("Thing {} disposed.", getThing().getUID());
90 public void handleCommand(ChannelUID channelUID, Command command) {
91 // When channel gets a refresh request, sending a null as the PentairPacket to updateChannel will force an
92 // updateState, regardless of previous packet value
93 if (command instanceof RefreshType) {
94 logger.debug("EasyTouch received refresh command");
96 updateChannel(channelUID.getId(), null);
101 if (command instanceof OnOffType onOffCommand) {
102 boolean state = onOffCommand == OnOffType.ON;
104 switch (channelUID.getId()) {
106 circuitSwitch(6, state);
109 circuitSwitch(1, state);
112 circuitSwitch(2, state);
115 circuitSwitch(3, state);
118 circuitSwitch(4, state);
121 circuitSwitch(5, state);
124 circuitSwitch(7, state);
127 circuitSwitch(8, state);
129 case EASYTOUCH_AUX7: // A5 01 10 20 86 02 09 01
130 circuitSwitch(9, state);
132 case EASYTOUCH_FEATURE1:
133 circuitSwitch(11, state);
135 case EASYTOUCH_FEATURE2:
136 circuitSwitch(12, state);
138 case EASYTOUCH_FEATURE3:
139 circuitSwitch(13, state);
141 case EASYTOUCH_FEATURE4:
142 circuitSwitch(14, state);
144 case EASYTOUCH_FEATURE5:
145 circuitSwitch(15, state);
147 case EASYTOUCH_FEATURE6:
148 circuitSwitch(16, state);
150 case EASYTOUCH_FEATURE7:
151 circuitSwitch(17, state);
153 case EASYTOUCH_FEATURE8:
154 circuitSwitch(18, state);
157 } else if (command instanceof DecimalType decimalCommand) {
158 int sp = decimalCommand.intValue();
160 switch (channelUID.getId()) {
161 case EASYTOUCH_SPASETPOINT:
164 case EASYTOUCH_POOLSETPOINT:
172 * Method to turn on/off a circuit in response to a command from the framework
174 * @param circuit circuit number
177 public void circuitSwitch(int circuit, boolean state) {
178 byte[] packet = { (byte) 0xA5, (byte) 0x01, (byte) id, (byte) 0x00 /* source */, (byte) 0x86, (byte) 0x02,
179 (byte) circuit, (byte) ((state) ? 1 : 0) };
181 PentairPacket p = new PentairPacket(packet);
183 PentairBaseBridgeHandler bbh = (PentairBaseBridgeHandler) this.getBridge().getHandler();
188 * Method to set heat point for pool (true) of spa (false)
190 * @param Pool pool=true, spa=false
193 public void setPoint(boolean pool, int temp) {
194 // [16,34,136,4,POOL HEAT Temp,SPA HEAT Temp,Heat Mode,0,2,56]
195 // [165, preambleByte, 16, 34, 136, 4, currentHeat.poolSetPoint, parseInt(req.params.temp), updateHeatMode, 0]
196 int spaset = (!pool) ? temp : phspcur.spasetpoint;
197 int poolset = (pool) ? temp : phspcur.poolsetpoint;
198 int heatmode = (phspcur.spaheatmode << 2) | phspcur.poolheatmode;
200 byte[] packet = { (byte) 0xA5, (byte) 0x01, (byte) id, (byte) 0x00 /* source */, (byte) 0x88, (byte) 0x04,
201 (byte) poolset, (byte) spaset, (byte) heatmode, (byte) 0 };
203 logger.info("Set {} temperature: {}", (pool) ? "Pool" : "Spa", temp);
205 PentairPacket p = new PentairPacket(packet);
207 PentairBaseBridgeHandler bbh = (PentairBaseBridgeHandler) this.getBridge().getHandler();
212 public void processPacketFrom(PentairPacket p) {
213 switch (p.getAction()) {
214 case 1: // Write command to pump
215 logger.trace("Write command to pump (unimplemented): {}", p);
218 if (p.getLength() != 29) {
219 logger.debug("Expected length of 29: {}", p);
224 * Save the previous state of the packet (p29cur) into a temp variable (p29old)
225 * Update the current state to the new packet we just received.
226 * Then call updateChannel which will compare the previous state (now p29old) to the new state (p29cur)
227 * to determine if updateState needs to be called
229 PentairPacketStatus p29Old = p29cur;
230 p29cur = new PentairPacketStatus(p);
232 updateChannel(EASYTOUCH_POOL, p29Old);
233 updateChannel(EASYTOUCH_POOLTEMP, p29Old);
234 updateChannel(EASYTOUCH_SPATEMP, p29Old);
235 updateChannel(EASYTOUCH_AIRTEMP, p29Old);
236 updateChannel(EASYTOUCH_SOLARTEMP, p29Old);
237 updateChannel(EASYTOUCH_HEATACTIVE, p29Old);
238 updateChannel(EASYTOUCH_POOL, p29Old);
239 updateChannel(EASYTOUCH_SPA, p29Old);
240 updateChannel(EASYTOUCH_AUX1, p29Old);
241 updateChannel(EASYTOUCH_AUX2, p29Old);
242 updateChannel(EASYTOUCH_AUX3, p29Old);
243 updateChannel(EASYTOUCH_AUX4, p29Old);
244 updateChannel(EASYTOUCH_AUX5, p29Old);
245 updateChannel(EASYTOUCH_AUX6, p29Old);
246 updateChannel(EASYTOUCH_AUX7, p29Old);
247 updateChannel(EASYTOUCH_FEATURE1, p29Old);
248 updateChannel(EASYTOUCH_FEATURE2, p29Old);
249 updateChannel(EASYTOUCH_FEATURE3, p29Old);
250 updateChannel(EASYTOUCH_FEATURE4, p29Old);
251 updateChannel(EASYTOUCH_FEATURE5, p29Old);
252 updateChannel(EASYTOUCH_FEATURE6, p29Old);
253 updateChannel(EASYTOUCH_FEATURE7, p29Old);
254 updateChannel(EASYTOUCH_FEATURE8, p29Old);
255 updateChannel(DIAG, p29Old);
258 case 4: // Pump control panel on/off
259 // No action - have not verified these commands, here for documentation purposes and future enhancement
260 logger.trace("Pump control panel on/of {}: {}", p.getDest(), p.getByte(PentairPacket.STARTOFDATA));
263 case 5: // Set pump mode
264 // No action - have not verified these commands, here for documentation purposes and future enhancement
265 logger.trace("Set pump mode {}: {}", p.getDest(), p.getByte(PentairPacket.STARTOFDATA));
268 case 6: // Set run mode
269 // No action - have not verified these commands, here for documentation purposes and future enhancement
270 logger.trace("Set run mode {}: {}", p.getDest(), p.getByte(PentairPacket.STARTOFDATA));
274 // No action - have not verified these commands, here for documentation purposes and future enhancement
275 logger.trace("Pump request status (unseen): {}", p);
277 case 8: // A5 01 0F 10 08 0D 4B 4B 4D 55 5E 07 00 00 58 00 00 00
278 if (p.getLength() != 0x0D) {
279 logger.debug("Expected length of 13: {}", p);
284 * Save the previous state of the packet (phspcur) into a temp variable (phspOld)
285 * Update the current state to the new packet we just received.
286 * Then call updateChannel which will compare the previous state (now phspold) to the new state
287 * (phspcur) to determine if updateState needs to be called
289 PentairPacketHeatSetPoint phspOld = phspcur;
290 phspcur = new PentairPacketHeatSetPoint(p);
292 updateChannel(EASYTOUCH_POOLSETPOINT, phspOld);
293 updateChannel(EASYTOUCH_SPASETPOINT, phspOld);
294 updateChannel(EASYTOUCH_SPAHEATMODE, phspOld);
295 updateChannel(EASYTOUCH_SPAHEATMODESTR, phspOld);
296 updateChannel(EASYTOUCH_POOLHEATMODE, phspOld);
297 updateChannel(EASYTOUCH_POOLHEATMODESTR, phspOld);
299 logger.debug("Heat set point: {}, {}, {}", p, phspcur.poolsetpoint, phspcur.spasetpoint);
302 logger.debug("Get Custom Names (unseen): {}", p);
305 logger.debug("Get Ciruit Names (unseen): {}", p);
308 logger.debug("Get Schedules (unseen): {}", p);
311 logger.debug("Set Circuit Function On/Off (unseen): {}", p);
314 logger.debug("Not Implemented: {}", p);
320 * Helper function to compare and update channel if needed. The class variables p29_cur and phsp_cur are used to
321 * determine the appropriate state of the channel.
323 * @param channel name of channel to be updated, corresponds to channel name in {@link PentairBindingConstants}
324 * @param p Packet representing the former state. If null, no compare is done and state is updated.
326 public void updateChannel(String channel, PentairPacket p) {
327 PentairPacketStatus p29 = null;
328 PentairPacketHeatSetPoint phsp = null;
331 if (p.getLength() == 29) {
332 p29 = (PentairPacketStatus) p;
333 } else if (p.getLength() == 13) {
334 phsp = (PentairPacketHeatSetPoint) p;
340 if (p29 == null || (p29.pool != p29cur.pool)) {
341 updateState(channel, (p29cur.pool) ? OnOffType.ON : OnOffType.OFF);
345 if (p29 == null || (p29.spa != p29cur.spa)) {
346 updateState(channel, (p29cur.spa) ? OnOffType.ON : OnOffType.OFF);
350 if (p29 == null || (p29.aux1 != p29cur.aux1)) {
351 updateState(channel, (p29cur.aux1) ? OnOffType.ON : OnOffType.OFF);
355 if (p29 == null || (p29.aux2 != p29cur.aux2)) {
356 updateState(channel, (p29cur.aux2) ? OnOffType.ON : OnOffType.OFF);
360 if (p29 == null || (p29.aux3 != p29cur.aux3)) {
361 updateState(channel, (p29cur.aux3) ? OnOffType.ON : OnOffType.OFF);
365 if (p29 == null || (p29.aux4 != p29cur.aux4)) {
366 updateState(channel, (p29cur.aux4) ? OnOffType.ON : OnOffType.OFF);
370 if (p29 == null || (p29.aux5 != p29cur.aux5)) {
371 updateState(channel, (p29cur.aux5) ? OnOffType.ON : OnOffType.OFF);
375 if (p29 == null || (p29.aux6 != p29cur.aux6)) {
376 updateState(channel, (p29cur.aux6) ? OnOffType.ON : OnOffType.OFF);
380 if (p29 == null || (p29.aux7 != p29cur.aux7)) {
381 updateState(channel, (p29cur.aux7) ? OnOffType.ON : OnOffType.OFF);
384 case EASYTOUCH_FEATURE1:
385 if (p29 == null || (p29.feature1 != p29cur.feature1)) {
386 updateState(channel, (p29cur.feature1) ? OnOffType.ON : OnOffType.OFF);
389 case EASYTOUCH_FEATURE2:
390 if (p29 == null || (p29.feature2 != p29cur.feature2)) {
391 updateState(channel, (p29cur.feature2) ? OnOffType.ON : OnOffType.OFF);
394 case EASYTOUCH_FEATURE3:
395 if (p29 == null || (p29.feature3 != p29cur.feature3)) {
396 updateState(channel, (p29cur.feature3) ? OnOffType.ON : OnOffType.OFF);
399 case EASYTOUCH_FEATURE4:
400 if (p29 == null || (p29.feature4 != p29cur.feature4)) {
401 updateState(channel, (p29cur.feature4) ? OnOffType.ON : OnOffType.OFF);
404 case EASYTOUCH_FEATURE5:
405 if (p29 == null || (p29.feature5 != p29cur.feature5)) {
406 updateState(channel, (p29cur.feature5) ? OnOffType.ON : OnOffType.OFF);
409 case EASYTOUCH_FEATURE6:
410 if (p29 == null || (p29.feature6 != p29cur.feature6)) {
411 updateState(channel, (p29cur.feature6) ? OnOffType.ON : OnOffType.OFF);
414 case EASYTOUCH_FEATURE7:
415 if (p29 == null || (p29.feature7 != p29cur.feature7)) {
416 updateState(channel, (p29cur.feature7) ? OnOffType.ON : OnOffType.OFF);
419 case EASYTOUCH_FEATURE8:
420 if (p29 == null || (p29.feature8 != p29cur.feature8)) {
421 updateState(channel, (p29cur.feature8) ? OnOffType.ON : OnOffType.OFF);
424 case EASYTOUCH_POOLTEMP:
425 if (p29 == null || (p29.pooltemp != p29cur.pooltemp)) {
427 updateState(channel, new DecimalType(p29cur.pooltemp));
429 updateState(channel, UnDefType.UNDEF);
433 case EASYTOUCH_SPATEMP:
434 if (p29 == null || (p29.spatemp != p29cur.spatemp)) {
436 updateState(channel, new DecimalType(p29cur.spatemp));
438 updateState(channel, UnDefType.UNDEF);
442 case EASYTOUCH_AIRTEMP:
443 if (p29 == null || (p29.airtemp != p29cur.airtemp)) {
444 updateState(channel, new DecimalType(p29cur.airtemp));
447 case EASYTOUCH_SOLARTEMP:
448 if (p29 == null || (p29.solartemp != p29cur.solartemp)) {
449 updateState(channel, new DecimalType(p29cur.solartemp));
452 case EASYTOUCH_SPAHEATMODE:
453 if (phsp == null || (phsp.spaheatmode != phspcur.spaheatmode)) {
454 updateState(channel, new DecimalType(phspcur.spaheatmode));
457 case EASYTOUCH_SPAHEATMODESTR:
458 if (phsp == null || (!Objects.equals(phsp.spaheatmodestr, phspcur.spaheatmodestr))) {
459 if (phspcur.spaheatmodestr != null) {
460 updateState(channel, new StringType(phspcur.spaheatmodestr));
464 case EASYTOUCH_POOLHEATMODE:
465 if (phsp == null || (phsp.poolheatmode != phspcur.poolheatmode)) {
466 updateState(channel, new DecimalType(phspcur.poolheatmode));
469 case EASYTOUCH_POOLHEATMODESTR:
470 if (phsp == null || (!Objects.equals(phsp.poolheatmodestr, phspcur.poolheatmodestr))) {
471 if (phspcur.poolheatmodestr != null) {
472 updateState(channel, new StringType(phspcur.poolheatmodestr));
476 case EASYTOUCH_HEATACTIVE:
477 if (p29 == null || (p29.heatactive != p29cur.heatactive)) {
478 updateState(channel, new DecimalType(p29cur.heatactive));
481 case EASYTOUCH_POOLSETPOINT:
482 if (phsp == null || (phsp.poolsetpoint != phspcur.poolsetpoint)) {
483 updateState(channel, new DecimalType(phspcur.poolsetpoint));
486 case EASYTOUCH_SPASETPOINT:
487 if (phsp == null || (phsp.spasetpoint != phspcur.spasetpoint)) {
488 updateState(channel, new DecimalType(phspcur.spasetpoint));
492 if (p29 == null || (p29.diag != p29cur.diag)) {
493 updateState(channel, new DecimalType(p29cur.diag));