2 * Copyright (c) 2010-2021 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;
20 import org.openhab.binding.pentair.internal.PentairBindingConstants;
21 import org.openhab.binding.pentair.internal.PentairPacket;
22 import org.openhab.binding.pentair.internal.PentairPacketHeatSetPoint;
23 import org.openhab.binding.pentair.internal.PentairPacketStatus;
24 import org.openhab.core.library.types.DecimalType;
25 import org.openhab.core.library.types.OnOffType;
26 import org.openhab.core.library.types.StringType;
27 import org.openhab.core.thing.ChannelUID;
28 import org.openhab.core.thing.Thing;
29 import org.openhab.core.thing.ThingStatus;
30 import org.openhab.core.thing.ThingStatusDetail;
31 import org.openhab.core.types.Command;
32 import org.openhab.core.types.RefreshType;
33 import org.openhab.core.types.UnDefType;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
38 * The {@link PentairEasyTouchHandler} is responsible for implementation of the EasyTouch Controller. It will handle
39 * commands sent to a thing and implements the different channels. It also parses/disposes of the packets seen on the
40 * bus from the controller.
42 * @author Jeff James - Initial contribution
44 public class PentairEasyTouchHandler extends PentairBaseThingHandler {
46 private final Logger logger = LoggerFactory.getLogger(PentairEasyTouchHandler.class);
49 * current/last status packet recieved, used to compare new packet values to determine if status needs to be updated
51 protected PentairPacketStatus p29cur = new PentairPacketStatus();
52 /** current/last heat set point packet, used to determine if status in framework should be updated */
53 protected PentairPacketHeatSetPoint phspcur = new PentairPacketHeatSetPoint();
55 public PentairEasyTouchHandler(Thing thing) {
60 public void initialize() {
61 logger.debug("Initializing EasyTouch - Thing ID: {}.", this.getThing().getUID());
63 id = ((BigDecimal) getConfig().get("id")).intValue();
65 // make sure there are no exisitng EasyTouch controllers
66 PentairBaseBridgeHandler bh = (PentairBaseBridgeHandler) this.getBridge().getHandler();
67 List<Thing> things = bh.getThing().getThings();
69 for (Thing t : things) {
70 if (t.getUID().equals(this.getThing().getUID())) {
73 if (t.getThingTypeUID().equals(EASYTOUCH_THING_TYPE)) {
74 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
75 "Another EasyTouch controller is already configured.");
80 updateStatus(ThingStatus.ONLINE);
84 public void dispose() {
85 logger.debug("Thing {} disposed.", getThing().getUID());
89 public void handleCommand(ChannelUID channelUID, Command command) {
90 // When channel gets a refresh request, sending a null as the PentairPacket to updateChannel will force an
91 // updateState, regardless of previous packet value
92 if (command instanceof RefreshType) {
93 logger.debug("EasyTouch received refresh command");
95 updateChannel(channelUID.getId(), null);
100 if (command instanceof OnOffType) {
101 boolean state = ((OnOffType) command) == OnOffType.ON;
103 switch (channelUID.getId()) {
105 circuitSwitch(6, state);
108 circuitSwitch(1, state);
111 circuitSwitch(2, state);
114 circuitSwitch(3, state);
117 circuitSwitch(4, state);
120 circuitSwitch(5, state);
123 circuitSwitch(7, state);
126 circuitSwitch(8, state);
128 case EASYTOUCH_AUX7: // A5 01 10 20 86 02 09 01
129 circuitSwitch(9, state);
131 case EASYTOUCH_FEATURE1:
132 circuitSwitch(11, state);
134 case EASYTOUCH_FEATURE2:
135 circuitSwitch(12, state);
137 case EASYTOUCH_FEATURE3:
138 circuitSwitch(13, state);
140 case EASYTOUCH_FEATURE4:
141 circuitSwitch(14, state);
143 case EASYTOUCH_FEATURE5:
144 circuitSwitch(15, state);
146 case EASYTOUCH_FEATURE6:
147 circuitSwitch(16, state);
149 case EASYTOUCH_FEATURE7:
150 circuitSwitch(17, state);
152 case EASYTOUCH_FEATURE8:
153 circuitSwitch(18, state);
156 } else if (command instanceof DecimalType) {
157 int sp = ((DecimalType) command).intValue();
159 switch (channelUID.getId()) {
160 case EASYTOUCH_SPASETPOINT:
163 case EASYTOUCH_POOLSETPOINT:
171 * Method to turn on/off a circuit in response to a command from the framework
173 * @param circuit circuit number
176 public void circuitSwitch(int circuit, boolean state) {
177 byte[] packet = { (byte) 0xA5, (byte) 0x01, (byte) id, (byte) 0x00 /* source */, (byte) 0x86, (byte) 0x02,
178 (byte) circuit, (byte) ((state) ? 1 : 0) };
180 PentairPacket p = new PentairPacket(packet);
182 PentairBaseBridgeHandler bbh = (PentairBaseBridgeHandler) this.getBridge().getHandler();
187 * Method to set heat point for pool (true) of spa (false)
189 * @param Pool pool=true, spa=false
192 public void setPoint(boolean pool, int temp) {
193 // [16,34,136,4,POOL HEAT Temp,SPA HEAT Temp,Heat Mode,0,2,56]
194 // [165, preambleByte, 16, 34, 136, 4, currentHeat.poolSetPoint, parseInt(req.params.temp), updateHeatMode, 0]
195 int spaset = (!pool) ? temp : phspcur.spasetpoint;
196 int poolset = (pool) ? temp : phspcur.poolsetpoint;
197 int heatmode = (phspcur.spaheatmode << 2) | phspcur.poolheatmode;
199 byte[] packet = { (byte) 0xA5, (byte) 0x01, (byte) id, (byte) 0x00 /* source */, (byte) 0x88, (byte) 0x04,
200 (byte) poolset, (byte) spaset, (byte) heatmode, (byte) 0 };
202 logger.info("Set {} temperature: {}", (pool) ? "Pool" : "Spa", temp);
204 PentairPacket p = new PentairPacket(packet);
206 PentairBaseBridgeHandler bbh = (PentairBaseBridgeHandler) this.getBridge().getHandler();
211 public void processPacketFrom(PentairPacket p) {
212 switch (p.getAction()) {
213 case 1: // Write command to pump
214 logger.trace("Write command to pump (unimplemented): {}", p);
217 if (p.getLength() != 29) {
218 logger.debug("Expected length of 29: {}", p);
223 * Save the previous state of the packet (p29cur) into a temp variable (p29old)
224 * Update the current state to the new packet we just received.
225 * Then call updateChannel which will compare the previous state (now p29old) to the new state (p29cur)
226 * to determine if updateState needs to be called
228 PentairPacketStatus p29Old = p29cur;
229 p29cur = new PentairPacketStatus(p);
231 updateChannel(EASYTOUCH_POOL, p29Old);
232 updateChannel(EASYTOUCH_POOLTEMP, p29Old);
233 updateChannel(EASYTOUCH_SPATEMP, p29Old);
234 updateChannel(EASYTOUCH_AIRTEMP, p29Old);
235 updateChannel(EASYTOUCH_SOLARTEMP, p29Old);
236 updateChannel(EASYTOUCH_HEATACTIVE, p29Old);
237 updateChannel(EASYTOUCH_POOL, p29Old);
238 updateChannel(EASYTOUCH_SPA, p29Old);
239 updateChannel(EASYTOUCH_AUX1, p29Old);
240 updateChannel(EASYTOUCH_AUX2, p29Old);
241 updateChannel(EASYTOUCH_AUX3, p29Old);
242 updateChannel(EASYTOUCH_AUX4, p29Old);
243 updateChannel(EASYTOUCH_AUX5, p29Old);
244 updateChannel(EASYTOUCH_AUX6, p29Old);
245 updateChannel(EASYTOUCH_AUX7, p29Old);
246 updateChannel(EASYTOUCH_FEATURE1, p29Old);
247 updateChannel(EASYTOUCH_FEATURE2, p29Old);
248 updateChannel(EASYTOUCH_FEATURE3, p29Old);
249 updateChannel(EASYTOUCH_FEATURE4, p29Old);
250 updateChannel(EASYTOUCH_FEATURE5, p29Old);
251 updateChannel(EASYTOUCH_FEATURE6, p29Old);
252 updateChannel(EASYTOUCH_FEATURE7, p29Old);
253 updateChannel(EASYTOUCH_FEATURE8, p29Old);
254 updateChannel(DIAG, p29Old);
257 case 4: // Pump control panel on/off
258 // No action - have not verified these commands, here for documentation purposes and future enhancement
259 logger.trace("Pump control panel on/of {}: {}", p.getDest(), p.getByte(PentairPacket.STARTOFDATA));
262 case 5: // Set pump mode
263 // No action - have not verified these commands, here for documentation purposes and future enhancement
264 logger.trace("Set pump mode {}: {}", p.getDest(), p.getByte(PentairPacket.STARTOFDATA));
267 case 6: // Set run mode
268 // No action - have not verified these commands, here for documentation purposes and future enhancement
269 logger.trace("Set run mode {}: {}", p.getDest(), p.getByte(PentairPacket.STARTOFDATA));
273 // No action - have not verified these commands, here for documentation purposes and future enhancement
274 logger.trace("Pump request status (unseen): {}", p);
276 case 8: // A5 01 0F 10 08 0D 4B 4B 4D 55 5E 07 00 00 58 00 00 00
277 if (p.getLength() != 0x0D) {
278 logger.debug("Expected length of 13: {}", p);
283 * Save the previous state of the packet (phspcur) into a temp variable (phspOld)
284 * Update the current state to the new packet we just received.
285 * Then call updateChannel which will compare the previous state (now phspold) to the new state
286 * (phspcur) to determine if updateState needs to be called
288 PentairPacketHeatSetPoint phspOld = phspcur;
289 phspcur = new PentairPacketHeatSetPoint(p);
291 updateChannel(EASYTOUCH_POOLSETPOINT, phspOld);
292 updateChannel(EASYTOUCH_SPASETPOINT, phspOld);
293 updateChannel(EASYTOUCH_SPAHEATMODE, phspOld);
294 updateChannel(EASYTOUCH_SPAHEATMODESTR, phspOld);
295 updateChannel(EASYTOUCH_POOLHEATMODE, phspOld);
296 updateChannel(EASYTOUCH_POOLHEATMODESTR, phspOld);
298 logger.debug("Heat set point: {}, {}, {}", p, phspcur.poolsetpoint, phspcur.spasetpoint);
301 logger.debug("Get Custom Names (unseen): {}", p);
304 logger.debug("Get Ciruit Names (unseen): {}", p);
307 logger.debug("Get Schedules (unseen): {}", p);
310 logger.debug("Set Circuit Function On/Off (unseen): {}", p);
313 logger.debug("Not Implemented: {}", p);
319 * Helper function to compare and update channel if needed. The class variables p29_cur and phsp_cur are used to
320 * determine the appropriate state of the channel.
322 * @param channel name of channel to be updated, corresponds to channel name in {@link PentairBindingConstants}
323 * @param p Packet representing the former state. If null, no compare is done and state is updated.
325 public void updateChannel(String channel, PentairPacket p) {
326 PentairPacketStatus p29 = null;
327 PentairPacketHeatSetPoint phsp = null;
330 if (p.getLength() == 29) {
331 p29 = (PentairPacketStatus) p;
332 } else if (p.getLength() == 13) {
333 phsp = (PentairPacketHeatSetPoint) p;
339 if (p29 == null || (p29.pool != p29cur.pool)) {
340 updateState(channel, (p29cur.pool) ? OnOffType.ON : OnOffType.OFF);
344 if (p29 == null || (p29.spa != p29cur.spa)) {
345 updateState(channel, (p29cur.spa) ? OnOffType.ON : OnOffType.OFF);
349 if (p29 == null || (p29.aux1 != p29cur.aux1)) {
350 updateState(channel, (p29cur.aux1) ? OnOffType.ON : OnOffType.OFF);
354 if (p29 == null || (p29.aux2 != p29cur.aux2)) {
355 updateState(channel, (p29cur.aux2) ? OnOffType.ON : OnOffType.OFF);
359 if (p29 == null || (p29.aux3 != p29cur.aux3)) {
360 updateState(channel, (p29cur.aux3) ? OnOffType.ON : OnOffType.OFF);
364 if (p29 == null || (p29.aux4 != p29cur.aux4)) {
365 updateState(channel, (p29cur.aux4) ? OnOffType.ON : OnOffType.OFF);
369 if (p29 == null || (p29.aux5 != p29cur.aux5)) {
370 updateState(channel, (p29cur.aux5) ? OnOffType.ON : OnOffType.OFF);
374 if (p29 == null || (p29.aux6 != p29cur.aux6)) {
375 updateState(channel, (p29cur.aux6) ? OnOffType.ON : OnOffType.OFF);
379 if (p29 == null || (p29.aux7 != p29cur.aux7)) {
380 updateState(channel, (p29cur.aux7) ? OnOffType.ON : OnOffType.OFF);
383 case EASYTOUCH_FEATURE1:
384 if (p29 == null || (p29.feature1 != p29cur.feature1)) {
385 updateState(channel, (p29cur.feature1) ? OnOffType.ON : OnOffType.OFF);
388 case EASYTOUCH_FEATURE2:
389 if (p29 == null || (p29.feature2 != p29cur.feature2)) {
390 updateState(channel, (p29cur.feature2) ? OnOffType.ON : OnOffType.OFF);
393 case EASYTOUCH_FEATURE3:
394 if (p29 == null || (p29.feature3 != p29cur.feature3)) {
395 updateState(channel, (p29cur.feature3) ? OnOffType.ON : OnOffType.OFF);
398 case EASYTOUCH_FEATURE4:
399 if (p29 == null || (p29.feature4 != p29cur.feature4)) {
400 updateState(channel, (p29cur.feature4) ? OnOffType.ON : OnOffType.OFF);
403 case EASYTOUCH_FEATURE5:
404 if (p29 == null || (p29.feature5 != p29cur.feature5)) {
405 updateState(channel, (p29cur.feature5) ? OnOffType.ON : OnOffType.OFF);
408 case EASYTOUCH_FEATURE6:
409 if (p29 == null || (p29.feature6 != p29cur.feature6)) {
410 updateState(channel, (p29cur.feature6) ? OnOffType.ON : OnOffType.OFF);
413 case EASYTOUCH_FEATURE7:
414 if (p29 == null || (p29.feature7 != p29cur.feature7)) {
415 updateState(channel, (p29cur.feature7) ? OnOffType.ON : OnOffType.OFF);
418 case EASYTOUCH_FEATURE8:
419 if (p29 == null || (p29.feature8 != p29cur.feature8)) {
420 updateState(channel, (p29cur.feature8) ? OnOffType.ON : OnOffType.OFF);
423 case EASYTOUCH_POOLTEMP:
424 if (p29 == null || (p29.pooltemp != p29cur.pooltemp)) {
426 updateState(channel, new DecimalType(p29cur.pooltemp));
428 updateState(channel, UnDefType.UNDEF);
432 case EASYTOUCH_SPATEMP:
433 if (p29 == null || (p29.spatemp != p29cur.spatemp)) {
435 updateState(channel, new DecimalType(p29cur.spatemp));
437 updateState(channel, UnDefType.UNDEF);
441 case EASYTOUCH_AIRTEMP:
442 if (p29 == null || (p29.airtemp != p29cur.airtemp)) {
443 updateState(channel, new DecimalType(p29cur.airtemp));
446 case EASYTOUCH_SOLARTEMP:
447 if (p29 == null || (p29.solartemp != p29cur.solartemp)) {
448 updateState(channel, new DecimalType(p29cur.solartemp));
451 case EASYTOUCH_SPAHEATMODE:
452 if (phsp == null || (phsp.spaheatmode != phspcur.spaheatmode)) {
453 updateState(channel, new DecimalType(phspcur.spaheatmode));
456 case EASYTOUCH_SPAHEATMODESTR:
457 if (phsp == null || (phsp.spaheatmodestr != phspcur.spaheatmodestr)) {
458 if (phspcur.spaheatmodestr != null) {
459 updateState(channel, new StringType(phspcur.spaheatmodestr));
463 case EASYTOUCH_POOLHEATMODE:
464 if (phsp == null || (phsp.poolheatmode != phspcur.poolheatmode)) {
465 updateState(channel, new DecimalType(phspcur.poolheatmode));
468 case EASYTOUCH_POOLHEATMODESTR:
469 if (phsp == null || (phsp.poolheatmodestr != phspcur.poolheatmodestr)) {
470 if (phspcur.poolheatmodestr != null) {
471 updateState(channel, new StringType(phspcur.poolheatmodestr));
475 case EASYTOUCH_HEATACTIVE:
476 if (p29 == null || (p29.heatactive != p29cur.heatactive)) {
477 updateState(channel, new DecimalType(p29cur.heatactive));
480 case EASYTOUCH_POOLSETPOINT:
481 if (phsp == null || (phsp.poolsetpoint != phspcur.poolsetpoint)) {
482 updateState(channel, new DecimalType(phspcur.poolsetpoint));
485 case EASYTOUCH_SPASETPOINT:
486 if (phsp == null || (phsp.spasetpoint != phspcur.spasetpoint)) {
487 updateState(channel, new DecimalType(phspcur.spasetpoint));
491 if (p29 == null || (p29.diag != p29cur.diag)) {
492 updateState(channel, new DecimalType(p29cur.diag));