]> git.basschouten.com Git - openhab-addons.git/blob
cf25382debfed7e264ae608b81688bf8c25387d3
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.pentair.internal.handler;
14
15 import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
16
17 import java.math.BigDecimal;
18 import java.util.List;
19
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;
36
37 /**
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.
41  *
42  * @author Jeff James - Initial contribution
43  */
44 public class PentairEasyTouchHandler extends PentairBaseThingHandler {
45
46     private final Logger logger = LoggerFactory.getLogger(PentairEasyTouchHandler.class);
47
48     /**
49      * current/last status packet recieved, used to compare new packet values to determine if status needs to be updated
50      */
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();
54
55     public PentairEasyTouchHandler(Thing thing) {
56         super(thing);
57     }
58
59     @Override
60     public void initialize() {
61         logger.debug("Initializing EasyTouch - Thing ID: {}.", this.getThing().getUID());
62
63         id = ((BigDecimal) getConfig().get("id")).intValue();
64
65         // make sure there are no exisitng EasyTouch controllers
66         PentairBaseBridgeHandler bh = (PentairBaseBridgeHandler) this.getBridge().getHandler();
67         List<Thing> things = bh.getThing().getThings();
68
69         for (Thing t : things) {
70             if (t.getUID().equals(this.getThing().getUID())) {
71                 continue;
72             }
73             if (t.getThingTypeUID().equals(EASYTOUCH_THING_TYPE)) {
74                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
75                         "Another EasyTouch controller is already configured.");
76                 return;
77             }
78         }
79
80         updateStatus(ThingStatus.ONLINE);
81     }
82
83     @Override
84     public void dispose() {
85         logger.debug("Thing {} disposed.", getThing().getUID());
86     }
87
88     @Override
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");
94
95             updateChannel(channelUID.getId(), null);
96
97             return;
98         }
99
100         if (command instanceof OnOffType) {
101             boolean state = ((OnOffType) command) == OnOffType.ON;
102
103             switch (channelUID.getId()) {
104                 case EASYTOUCH_POOL:
105                     circuitSwitch(6, state);
106                     break;
107                 case EASYTOUCH_SPA:
108                     circuitSwitch(1, state);
109                     break;
110                 case EASYTOUCH_AUX1:
111                     circuitSwitch(2, state);
112                     break;
113                 case EASYTOUCH_AUX2:
114                     circuitSwitch(3, state);
115                     break;
116                 case EASYTOUCH_AUX3:
117                     circuitSwitch(4, state);
118                     break;
119                 case EASYTOUCH_AUX4:
120                     circuitSwitch(5, state);
121                     break;
122                 case EASYTOUCH_AUX5:
123                     circuitSwitch(7, state);
124                     break;
125                 case EASYTOUCH_AUX6:
126                     circuitSwitch(8, state);
127                     break;
128                 case EASYTOUCH_AUX7: // A5 01 10 20 86 02 09 01
129                     circuitSwitch(9, state);
130                     break;
131                 case EASYTOUCH_FEATURE1:
132                     circuitSwitch(11, state);
133                     break;
134                 case EASYTOUCH_FEATURE2:
135                     circuitSwitch(12, state);
136                     break;
137                 case EASYTOUCH_FEATURE3:
138                     circuitSwitch(13, state);
139                     break;
140                 case EASYTOUCH_FEATURE4:
141                     circuitSwitch(14, state);
142                     break;
143                 case EASYTOUCH_FEATURE5:
144                     circuitSwitch(15, state);
145                     break;
146                 case EASYTOUCH_FEATURE6:
147                     circuitSwitch(16, state);
148                     break;
149                 case EASYTOUCH_FEATURE7:
150                     circuitSwitch(17, state);
151                     break;
152                 case EASYTOUCH_FEATURE8:
153                     circuitSwitch(18, state);
154                     break;
155             }
156         } else if (command instanceof DecimalType) {
157             int sp = ((DecimalType) command).intValue();
158
159             switch (channelUID.getId()) {
160                 case EASYTOUCH_SPASETPOINT:
161                     setPoint(false, sp);
162                     break;
163                 case EASYTOUCH_POOLSETPOINT:
164                     setPoint(true, sp);
165                     break;
166             }
167         }
168     }
169
170     /**
171      * Method to turn on/off a circuit in response to a command from the framework
172      *
173      * @param circuit circuit number
174      * @param state
175      */
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) };
179
180         PentairPacket p = new PentairPacket(packet);
181
182         PentairBaseBridgeHandler bbh = (PentairBaseBridgeHandler) this.getBridge().getHandler();
183         bbh.writePacket(p);
184     }
185
186     /**
187      * Method to set heat point for pool (true) of spa (false)
188      *
189      * @param Pool pool=true, spa=false
190      * @param temp
191      */
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;
198
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 };
201
202         logger.info("Set {} temperature: {}", (pool) ? "Pool" : "Spa", temp);
203
204         PentairPacket p = new PentairPacket(packet);
205
206         PentairBaseBridgeHandler bbh = (PentairBaseBridgeHandler) this.getBridge().getHandler();
207         bbh.writePacket(p);
208     }
209
210     @Override
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);
215                 break;
216             case 2:
217                 if (p.getLength() != 29) {
218                     logger.debug("Expected length of 29: {}", p);
219                     return;
220                 }
221
222                 /*
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
227                  */
228                 PentairPacketStatus p29Old = p29cur;
229                 p29cur = new PentairPacketStatus(p);
230
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);
255
256                 break;
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));
260
261                 break;
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));
265
266                 break;
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));
270
271                 break;
272             case 7:
273                 // No action - have not verified these commands, here for documentation purposes and future enhancement
274                 logger.trace("Pump request status (unseen): {}", p);
275                 break;
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);
279                     return;
280                 }
281
282                 /*
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
287                  */
288                 PentairPacketHeatSetPoint phspOld = phspcur;
289                 phspcur = new PentairPacketHeatSetPoint(p);
290
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);
297
298                 logger.debug("Heat set point: {}, {}, {}", p, phspcur.poolsetpoint, phspcur.spasetpoint);
299                 break;
300             case 10:
301                 logger.debug("Get Custom Names (unseen): {}", p);
302                 break;
303             case 11:
304                 logger.debug("Get Ciruit Names (unseen): {}", p);
305                 break;
306             case 17:
307                 logger.debug("Get Schedules (unseen): {}", p);
308                 break;
309             case 134:
310                 logger.debug("Set Circuit Function On/Off (unseen): {}", p);
311                 break;
312             default:
313                 logger.debug("Not Implemented: {}", p);
314                 break;
315         }
316     }
317
318     /**
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.
321      *
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.
324      */
325     public void updateChannel(String channel, PentairPacket p) {
326         PentairPacketStatus p29 = null;
327         PentairPacketHeatSetPoint phsp = null;
328
329         if (p != null) {
330             if (p.getLength() == 29) {
331                 p29 = (PentairPacketStatus) p;
332             } else if (p.getLength() == 13) {
333                 phsp = (PentairPacketHeatSetPoint) p;
334             }
335         }
336
337         switch (channel) {
338             case EASYTOUCH_POOL:
339                 if (p29 == null || (p29.pool != p29cur.pool)) {
340                     updateState(channel, (p29cur.pool) ? OnOffType.ON : OnOffType.OFF);
341                 }
342                 break;
343             case EASYTOUCH_SPA:
344                 if (p29 == null || (p29.spa != p29cur.spa)) {
345                     updateState(channel, (p29cur.spa) ? OnOffType.ON : OnOffType.OFF);
346                 }
347                 break;
348             case EASYTOUCH_AUX1:
349                 if (p29 == null || (p29.aux1 != p29cur.aux1)) {
350                     updateState(channel, (p29cur.aux1) ? OnOffType.ON : OnOffType.OFF);
351                 }
352                 break;
353             case EASYTOUCH_AUX2:
354                 if (p29 == null || (p29.aux2 != p29cur.aux2)) {
355                     updateState(channel, (p29cur.aux2) ? OnOffType.ON : OnOffType.OFF);
356                 }
357                 break;
358             case EASYTOUCH_AUX3:
359                 if (p29 == null || (p29.aux3 != p29cur.aux3)) {
360                     updateState(channel, (p29cur.aux3) ? OnOffType.ON : OnOffType.OFF);
361                 }
362                 break;
363             case EASYTOUCH_AUX4:
364                 if (p29 == null || (p29.aux4 != p29cur.aux4)) {
365                     updateState(channel, (p29cur.aux4) ? OnOffType.ON : OnOffType.OFF);
366                 }
367                 break;
368             case EASYTOUCH_AUX5:
369                 if (p29 == null || (p29.aux5 != p29cur.aux5)) {
370                     updateState(channel, (p29cur.aux5) ? OnOffType.ON : OnOffType.OFF);
371                 }
372                 break;
373             case EASYTOUCH_AUX6:
374                 if (p29 == null || (p29.aux6 != p29cur.aux6)) {
375                     updateState(channel, (p29cur.aux6) ? OnOffType.ON : OnOffType.OFF);
376                 }
377                 break;
378             case EASYTOUCH_AUX7:
379                 if (p29 == null || (p29.aux7 != p29cur.aux7)) {
380                     updateState(channel, (p29cur.aux7) ? OnOffType.ON : OnOffType.OFF);
381                 }
382                 break;
383             case EASYTOUCH_FEATURE1:
384                 if (p29 == null || (p29.feature1 != p29cur.feature1)) {
385                     updateState(channel, (p29cur.feature1) ? OnOffType.ON : OnOffType.OFF);
386                 }
387                 break;
388             case EASYTOUCH_FEATURE2:
389                 if (p29 == null || (p29.feature2 != p29cur.feature2)) {
390                     updateState(channel, (p29cur.feature2) ? OnOffType.ON : OnOffType.OFF);
391                 }
392                 break;
393             case EASYTOUCH_FEATURE3:
394                 if (p29 == null || (p29.feature3 != p29cur.feature3)) {
395                     updateState(channel, (p29cur.feature3) ? OnOffType.ON : OnOffType.OFF);
396                 }
397                 break;
398             case EASYTOUCH_FEATURE4:
399                 if (p29 == null || (p29.feature4 != p29cur.feature4)) {
400                     updateState(channel, (p29cur.feature4) ? OnOffType.ON : OnOffType.OFF);
401                 }
402                 break;
403             case EASYTOUCH_FEATURE5:
404                 if (p29 == null || (p29.feature5 != p29cur.feature5)) {
405                     updateState(channel, (p29cur.feature5) ? OnOffType.ON : OnOffType.OFF);
406                 }
407                 break;
408             case EASYTOUCH_FEATURE6:
409                 if (p29 == null || (p29.feature6 != p29cur.feature6)) {
410                     updateState(channel, (p29cur.feature6) ? OnOffType.ON : OnOffType.OFF);
411                 }
412                 break;
413             case EASYTOUCH_FEATURE7:
414                 if (p29 == null || (p29.feature7 != p29cur.feature7)) {
415                     updateState(channel, (p29cur.feature7) ? OnOffType.ON : OnOffType.OFF);
416                 }
417                 break;
418             case EASYTOUCH_FEATURE8:
419                 if (p29 == null || (p29.feature8 != p29cur.feature8)) {
420                     updateState(channel, (p29cur.feature8) ? OnOffType.ON : OnOffType.OFF);
421                 }
422                 break;
423             case EASYTOUCH_POOLTEMP:
424                 if (p29 == null || (p29.pooltemp != p29cur.pooltemp)) {
425                     if (p29cur.pool) {
426                         updateState(channel, new DecimalType(p29cur.pooltemp));
427                     } else {
428                         updateState(channel, UnDefType.UNDEF);
429                     }
430                 }
431                 break;
432             case EASYTOUCH_SPATEMP:
433                 if (p29 == null || (p29.spatemp != p29cur.spatemp)) {
434                     if (p29cur.spa) {
435                         updateState(channel, new DecimalType(p29cur.spatemp));
436                     } else {
437                         updateState(channel, UnDefType.UNDEF);
438                     }
439                 }
440                 break;
441             case EASYTOUCH_AIRTEMP:
442                 if (p29 == null || (p29.airtemp != p29cur.airtemp)) {
443                     updateState(channel, new DecimalType(p29cur.airtemp));
444                 }
445                 break;
446             case EASYTOUCH_SOLARTEMP:
447                 if (p29 == null || (p29.solartemp != p29cur.solartemp)) {
448                     updateState(channel, new DecimalType(p29cur.solartemp));
449                 }
450                 break;
451             case EASYTOUCH_SPAHEATMODE:
452                 if (phsp == null || (phsp.spaheatmode != phspcur.spaheatmode)) {
453                     updateState(channel, new DecimalType(phspcur.spaheatmode));
454                 }
455                 break;
456             case EASYTOUCH_SPAHEATMODESTR:
457                 if (phsp == null || (phsp.spaheatmodestr != phspcur.spaheatmodestr)) {
458                     if (phspcur.spaheatmodestr != null) {
459                         updateState(channel, new StringType(phspcur.spaheatmodestr));
460                     }
461                 }
462                 break;
463             case EASYTOUCH_POOLHEATMODE:
464                 if (phsp == null || (phsp.poolheatmode != phspcur.poolheatmode)) {
465                     updateState(channel, new DecimalType(phspcur.poolheatmode));
466                 }
467                 break;
468             case EASYTOUCH_POOLHEATMODESTR:
469                 if (phsp == null || (phsp.poolheatmodestr != phspcur.poolheatmodestr)) {
470                     if (phspcur.poolheatmodestr != null) {
471                         updateState(channel, new StringType(phspcur.poolheatmodestr));
472                     }
473                 }
474                 break;
475             case EASYTOUCH_HEATACTIVE:
476                 if (p29 == null || (p29.heatactive != p29cur.heatactive)) {
477                     updateState(channel, new DecimalType(p29cur.heatactive));
478                 }
479                 break;
480             case EASYTOUCH_POOLSETPOINT:
481                 if (phsp == null || (phsp.poolsetpoint != phspcur.poolsetpoint)) {
482                     updateState(channel, new DecimalType(phspcur.poolsetpoint));
483                 }
484                 break;
485             case EASYTOUCH_SPASETPOINT:
486                 if (phsp == null || (phsp.spasetpoint != phspcur.spasetpoint)) {
487                     updateState(channel, new DecimalType(phspcur.spasetpoint));
488                 }
489                 break;
490             case DIAG:
491                 if (p29 == null || (p29.diag != p29cur.diag)) {
492                     updateState(channel, new DecimalType(p29cur.diag));
493                 }
494                 break;
495         }
496     }
497 }