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