]> git.basschouten.com Git - openhab-addons.git/blob
cbb49659f630d42235e5eb750bb9ea9d9f62353b
[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.atlona.internal.pro3;
14
15 import java.io.IOException;
16 import java.util.concurrent.ScheduledFuture;
17 import java.util.concurrent.TimeUnit;
18 import java.util.regex.Matcher;
19 import java.util.regex.Pattern;
20
21 import org.openhab.binding.atlona.internal.AtlonaHandlerCallback;
22 import org.openhab.binding.atlona.internal.StatefulHandlerCallback;
23 import org.openhab.binding.atlona.internal.handler.AtlonaHandler;
24 import org.openhab.binding.atlona.internal.net.SocketChannelSession;
25 import org.openhab.binding.atlona.internal.net.SocketSession;
26 import org.openhab.core.library.types.DecimalType;
27 import org.openhab.core.library.types.OnOffType;
28 import org.openhab.core.library.types.StringType;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.types.Command;
34 import org.openhab.core.types.RefreshType;
35 import org.openhab.core.types.State;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 /**
40  * The {@link org.openhab.binding.atlona.internal.pro3.AtlonaPro3Handler} is responsible for handling commands, which
41  * are sent to one of the channels.
42  *
43  * @author Tim Roberts - Initial contribution
44  */
45 public class AtlonaPro3Handler extends AtlonaHandler<AtlonaPro3Capabilities> {
46
47     private final Logger logger = LoggerFactory.getLogger(AtlonaPro3Handler.class);
48
49     /**
50      * The {@link AtlonaPro3PortocolHandler} protocol handler
51      */
52     private AtlonaPro3PortocolHandler atlonaHandler;
53
54     /**
55      * The {@link SocketSession} telnet session to the switch. Will be null if not connected.
56      */
57     private SocketSession session;
58
59     /**
60      * The polling job to poll the actual state from the {@link #session}
61      */
62     private ScheduledFuture<?> polling;
63
64     /**
65      * The retry connection event
66      */
67     private ScheduledFuture<?> retryConnection;
68
69     /**
70      * The ping event
71      */
72     private ScheduledFuture<?> ping;
73
74     // List of all the groups patterns we recognize
75     private static final Pattern GROUP_PRIMARY_PATTERN = Pattern.compile("^" + AtlonaPro3Constants.GROUP_PRIMARY + "$");
76     private static final Pattern GROUP_PORT_PATTERN = Pattern
77             .compile("^" + AtlonaPro3Constants.GROUP_PORT + "(\\d{1,2})$");
78     private static final Pattern GROUP_MIRROR_PATTERN = Pattern
79             .compile("^" + AtlonaPro3Constants.GROUP_MIRROR + "(\\d{1,2})$");
80     private static final Pattern GROUP_VOLUME_PATTERN = Pattern
81             .compile("^" + AtlonaPro3Constants.GROUP_VOLUME + "(\\d{1,2})$");
82
83     // List of preset commands we recognize
84     private static final Pattern CMD_PRESETSAVE = Pattern
85             .compile("^" + AtlonaPro3Constants.CMD_PRESETSAVE + "(\\d{1,2})$");
86     private static final Pattern CMD_PRESETRECALL = Pattern
87             .compile("^" + AtlonaPro3Constants.CMD_PRESETRECALL + "(\\d{1,2})$");
88     private static final Pattern CMD_PRESETCLEAR = Pattern
89             .compile("^" + AtlonaPro3Constants.CMD_PRESETCLEAR + "(\\d{1,2})$");
90
91     // List of matrix commands we recognize
92     private static final Pattern CMD_MATRIXRESET = Pattern.compile("^" + AtlonaPro3Constants.CMD_MATRIXRESET + "$");
93     private static final Pattern CMD_MATRIXRESETPORTS = Pattern
94             .compile("^" + AtlonaPro3Constants.CMD_MATRIXRESETPORTS + "$");
95     private static final Pattern CMD_MATRIXPORTALL = Pattern
96             .compile("^" + AtlonaPro3Constants.CMD_MATRIXPORTALL + "(\\d{1,2})$");
97
98     /**
99      * Constructs the handler from the {@link org.openhab.core.thing.Thing} with the number of power ports and
100      * audio ports the switch supports.
101      *
102      * @param thing a non-null {@link org.openhab.core.thing.Thing} the handler is for
103      * @param capabilities a non-null {@link org.openhab.binding.atlona.internal.pro3.AtlonaPro3Capabilities}
104      */
105     public AtlonaPro3Handler(Thing thing, AtlonaPro3Capabilities capabilities) {
106         super(thing, capabilities);
107
108         if (thing == null) {
109             throw new IllegalArgumentException("thing cannot be null");
110         }
111     }
112
113     /**
114      * {@inheritDoc}
115      *
116      * Handles commands to specific channels. This implementation will offload much of its work to the
117      * {@link AtlonaPro3PortocolHandler}. Basically we validate the type of command for the channel then call the
118      * {@link AtlonaPro3PortocolHandler} to handle the actual protocol. Special use case is the {@link RefreshType}
119      * where we call {{@link #handleRefresh(ChannelUID)} to handle a refresh of the specific channel (which in turn
120      * calls
121      * {@link AtlonaPro3PortocolHandler} to handle the actual refresh
122      */
123     @Override
124     public void handleCommand(ChannelUID channelUID, Command command) {
125         if (command instanceof RefreshType) {
126             handleRefresh(channelUID);
127             return;
128         }
129
130         final String group = channelUID.getGroupId().toLowerCase();
131         final String id = channelUID.getIdWithoutGroup().toLowerCase();
132
133         Matcher m;
134         if ((m = GROUP_PRIMARY_PATTERN.matcher(group)).matches()) {
135             switch (id) {
136                 case AtlonaPro3Constants.CHANNEL_POWER:
137                     if (command instanceof OnOffType onOffCommand) {
138                         final boolean makeOn = onOffCommand == OnOffType.ON;
139                         atlonaHandler.setPower(makeOn);
140                     } else {
141                         logger.debug("Received a POWER channel command with a non OnOffType: {}", command);
142                     }
143
144                     break;
145
146                 case AtlonaPro3Constants.CHANNEL_PANELLOCK:
147                     if (command instanceof OnOffType onOffCommand) {
148                         final boolean makeOn = onOffCommand == OnOffType.ON;
149                         atlonaHandler.setPanelLock(makeOn);
150                     } else {
151                         logger.debug("Received a PANELLOCK channel command with a non OnOffType: {}", command);
152                     }
153                     break;
154
155                 case AtlonaPro3Constants.CHANNEL_IRENABLE:
156                     if (command instanceof OnOffType onOffCommand) {
157                         final boolean makeOn = onOffCommand == OnOffType.ON;
158                         atlonaHandler.setIrOn(makeOn);
159                     } else {
160                         logger.debug("Received an IRLOCK channel command with a non OnOffType: {}", command);
161                     }
162
163                     break;
164                 case AtlonaPro3Constants.CHANNEL_MATRIXCMDS:
165                     if (command instanceof StringType) {
166                         final String matrixCmd = command.toString();
167                         Matcher cmd;
168                         try {
169                             if ((cmd = CMD_MATRIXRESET.matcher(matrixCmd)).matches()) {
170                                 atlonaHandler.resetMatrix();
171                             } else if ((cmd = CMD_MATRIXRESETPORTS.matcher(matrixCmd)).matches()) {
172                                 atlonaHandler.resetAllPorts();
173                             } else if ((cmd = CMD_MATRIXPORTALL.matcher(matrixCmd)).matches()) {
174                                 if (cmd.groupCount() == 1) {
175                                     final int portNbr = Integer.parseInt(cmd.group(1));
176                                     atlonaHandler.setPortAll(portNbr);
177                                 } else {
178                                     logger.debug("Unknown matirx set port command: '{}'", matrixCmd);
179                                 }
180
181                             } else {
182                                 logger.debug("Unknown matrix command: '{}'", cmd);
183                             }
184                         } catch (NumberFormatException e) {
185                             logger.debug("Could not parse the port number from the command: '{}'", matrixCmd);
186                         }
187                     }
188                     break;
189                 case AtlonaPro3Constants.CHANNEL_PRESETCMDS:
190                     if (command instanceof StringType) {
191                         final String presetCmd = command.toString();
192                         Matcher cmd;
193                         try {
194                             if ((cmd = CMD_PRESETSAVE.matcher(presetCmd)).matches()) {
195                                 if (cmd.groupCount() == 1) {
196                                     final int presetNbr = Integer.parseInt(cmd.group(1));
197                                     atlonaHandler.saveIoSettings(presetNbr);
198                                 } else {
199                                     logger.debug("Unknown preset save command: '{}'", presetCmd);
200                                 }
201                             } else if ((cmd = CMD_PRESETRECALL.matcher(presetCmd)).matches()) {
202                                 if (cmd.groupCount() == 1) {
203                                     final int presetNbr = Integer.parseInt(cmd.group(1));
204                                     atlonaHandler.recallIoSettings(presetNbr);
205                                 } else {
206                                     logger.debug("Unknown preset recall command: '{}'", presetCmd);
207                                 }
208                             } else if ((cmd = CMD_PRESETCLEAR.matcher(presetCmd)).matches()) {
209                                 if (cmd.groupCount() == 1) {
210                                     final int presetNbr = Integer.parseInt(cmd.group(1));
211                                     atlonaHandler.clearIoSettings(presetNbr);
212                                 } else {
213                                     logger.debug("Unknown preset clear command: '{}'", presetCmd);
214                                 }
215
216                             } else {
217                                 logger.debug("Unknown preset command: '{}'", cmd);
218                             }
219                         } catch (NumberFormatException e) {
220                             logger.debug("Could not parse the preset number from the command: '{}'", presetCmd);
221                         }
222                     }
223                     break;
224
225                 default:
226                     logger.debug("Unknown/Unsupported Primary Channel: {}", channelUID.getAsString());
227                     break;
228             }
229         } else if ((m = GROUP_PORT_PATTERN.matcher(group)).matches()) {
230             if (m.groupCount() == 1) {
231                 try {
232                     final int portNbr = Integer.parseInt(m.group(1));
233
234                     switch (id) {
235                         case AtlonaPro3Constants.CHANNEL_PORTOUTPUT:
236                             if (command instanceof DecimalType decimalCommand) {
237                                 final int inpNbr = decimalCommand.intValue();
238                                 atlonaHandler.setPortSwitch(inpNbr, portNbr);
239                             } else {
240                                 logger.debug("Received a PORTOUTPUT channel command with a non DecimalType: {}",
241                                         command);
242                             }
243
244                             break;
245
246                         case AtlonaPro3Constants.CHANNEL_PORTPOWER:
247                             if (command instanceof OnOffType onOffCommand) {
248                                 final boolean makeOn = onOffCommand == OnOffType.ON;
249                                 atlonaHandler.setPortPower(portNbr, makeOn);
250                             } else {
251                                 logger.debug("Received a PORTPOWER channel command with a non OnOffType: {}", command);
252                             }
253                             break;
254                         default:
255                             logger.debug("Unknown/Unsupported Port Channel: {}", channelUID.getAsString());
256                             break;
257                     }
258                 } catch (NumberFormatException e) {
259                     logger.debug("Bad Port Channel (can't parse the port nbr): {}", channelUID.getAsString());
260                 }
261             }
262         } else if ((m = GROUP_MIRROR_PATTERN.matcher(group)).matches()) {
263             if (m.groupCount() == 1) {
264                 try {
265                     final int hdmiPortNbr = Integer.parseInt(m.group(1));
266
267                     switch (id) {
268                         case AtlonaPro3Constants.CHANNEL_PORTMIRROR:
269                             if (command instanceof DecimalType decimalCommand) {
270                                 final int outPortNbr = decimalCommand.intValue();
271                                 if (outPortNbr <= 0) {
272                                     atlonaHandler.removePortMirror(hdmiPortNbr);
273                                 } else {
274                                     atlonaHandler.setPortMirror(hdmiPortNbr, outPortNbr);
275                                 }
276                             } else {
277                                 logger.debug("Received a PORTMIRROR channel command with a non DecimalType: {}",
278                                         command);
279                             }
280
281                             break;
282                         case AtlonaPro3Constants.CHANNEL_PORTMIRRORENABLED:
283                             if (command instanceof OnOffType) {
284                                 if (command == OnOffType.ON) {
285                                     final StatefulHandlerCallback callback = (StatefulHandlerCallback) atlonaHandler
286                                             .getCallback();
287                                     final State state = callback.getState(AtlonaPro3Constants.CHANNEL_PORTMIRROR);
288                                     int outPortNbr = 1;
289                                     if (state instanceof DecimalType decimalCommand) {
290                                         outPortNbr = decimalCommand.intValue();
291                                     }
292                                     atlonaHandler.setPortMirror(hdmiPortNbr, outPortNbr);
293                                 } else {
294                                     atlonaHandler.removePortMirror(hdmiPortNbr);
295                                 }
296                             } else {
297                                 logger.debug("Received a PORTMIRROR channel command with a non DecimalType: {}",
298                                         command);
299                             }
300
301                             break;
302                         default:
303                             logger.debug("Unknown/Unsupported Mirror Channel: {}", channelUID.getAsString());
304                             break;
305                     }
306                 } catch (NumberFormatException e) {
307                     logger.debug("Bad Mirror Channel (can't parse the port nbr): {}", channelUID.getAsString());
308                 }
309             }
310         } else if ((m = GROUP_VOLUME_PATTERN.matcher(group)).matches()) {
311             if (m.groupCount() == 1) {
312                 try {
313                     final int portNbr = Integer.parseInt(m.group(1));
314
315                     switch (id) {
316                         case AtlonaPro3Constants.CHANNEL_VOLUME_MUTE:
317                             if (command instanceof OnOffType onOffCommand) {
318                                 atlonaHandler.setVolumeMute(portNbr, onOffCommand == OnOffType.ON);
319                             } else {
320                                 logger.debug("Received a VOLUME MUTE channel command with a non OnOffType: {}",
321                                         command);
322                             }
323
324                             break;
325                         case AtlonaPro3Constants.CHANNEL_VOLUME:
326                             if (command instanceof DecimalType decimalCommand) {
327                                 final int level = decimalCommand.intValue();
328                                 atlonaHandler.setVolume(portNbr, level);
329                             } else {
330                                 logger.debug("Received a VOLUME channel command with a non DecimalType: {}", command);
331                             }
332                             break;
333
334                         default:
335                             logger.debug("Unknown/Unsupported Volume Channel: {}", channelUID.getAsString());
336                             break;
337                     }
338                 } catch (NumberFormatException e) {
339                     logger.debug("Bad Volume Channel (can't parse the port nbr): {}", channelUID.getAsString());
340                 }
341             }
342         } else {
343             logger.debug("Unknown/Unsupported Channel: {}", channelUID.getAsString());
344         }
345     }
346
347     /**
348      * Method that handles the {@link RefreshType} command specifically. Calls the {@link AtlonaPro3PortocolHandler} to
349      * handle the actual refresh based on the channel id.
350      *
351      * @param id a non-null, possibly empty channel id to refresh
352      */
353     private void handleRefresh(ChannelUID channelUID) {
354         if (getThing().getStatus() != ThingStatus.ONLINE) {
355             return;
356         }
357
358         final String group = channelUID.getGroupId().toLowerCase();
359         final String id = channelUID.getIdWithoutGroup().toLowerCase();
360         final StatefulHandlerCallback callback = (StatefulHandlerCallback) atlonaHandler.getCallback();
361
362         Matcher m;
363         if ((m = GROUP_PRIMARY_PATTERN.matcher(group)).matches()) {
364             switch (id) {
365                 case AtlonaPro3Constants.CHANNEL_POWER:
366                     callback.removeState(AtlonaPro3Utilities.createChannelID(group, id));
367                     atlonaHandler.refreshPower();
368                     break;
369
370                 default:
371                     break;
372             }
373
374         } else if ((m = GROUP_PORT_PATTERN.matcher(group)).matches()) {
375             if (m.groupCount() == 1) {
376                 try {
377                     final int portNbr = Integer.parseInt(m.group(1));
378                     callback.removeState(AtlonaPro3Utilities.createChannelID(group, portNbr, id));
379
380                     switch (id) {
381                         case AtlonaPro3Constants.CHANNEL_PORTOUTPUT:
382                             atlonaHandler.refreshPortStatus(portNbr);
383                             break;
384
385                         case AtlonaPro3Constants.CHANNEL_PORTPOWER:
386                             atlonaHandler.refreshPortPower(portNbr);
387                             break;
388                         default:
389                             break;
390                     }
391                 } catch (NumberFormatException e) {
392                     logger.debug("Bad Port Channel (can't parse the port nbr): {}", channelUID.getAsString());
393                 }
394
395             }
396         } else if ((m = GROUP_MIRROR_PATTERN.matcher(group)).matches()) {
397             if (m.groupCount() == 1) {
398                 try {
399                     final int hdmiPortNbr = Integer.parseInt(m.group(1));
400                     callback.removeState(AtlonaPro3Utilities.createChannelID(group, hdmiPortNbr, id));
401                     atlonaHandler.refreshPortMirror(hdmiPortNbr);
402                 } catch (NumberFormatException e) {
403                     logger.debug("Bad Mirror Channel (can't parse the port nbr): {}", channelUID.getAsString());
404                 }
405
406             }
407         } else if ((m = GROUP_VOLUME_PATTERN.matcher(group)).matches()) {
408             if (m.groupCount() == 1) {
409                 try {
410                     final int portNbr = Integer.parseInt(m.group(1));
411                     callback.removeState(AtlonaPro3Utilities.createChannelID(group, portNbr, id));
412
413                     switch (id) {
414                         case AtlonaPro3Constants.CHANNEL_VOLUME_MUTE:
415                             atlonaHandler.refreshVolumeMute(portNbr);
416                             break;
417                         case AtlonaPro3Constants.CHANNEL_VOLUME:
418                             atlonaHandler.refreshVolumeStatus(portNbr);
419                             break;
420
421                         default:
422                             break;
423                     }
424                 } catch (NumberFormatException e) {
425                     logger.debug("Bad Volume Channel (can't parse the port nbr): {}", channelUID.getAsString());
426                 }
427
428             }
429         }
430     }
431
432     /**
433      * {@inheritDoc}
434      *
435      * Initializes the handler. This initialization will read/validate the configuration, then will create the
436      * {@link SocketSession}, initialize the {@link AtlonaPro3PortocolHandler} and will attempt to connect to the switch
437      * (via {{@link #retryConnect()}.
438      */
439     @Override
440     public void initialize() {
441         final AtlonaPro3Config config = getAtlonaConfig();
442
443         if (config == null) {
444             return;
445         }
446
447         if (config.getIpAddress() == null || config.getIpAddress().trim().length() == 0) {
448             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
449                     "IP Address of Atlona Pro3 is missing from configuration");
450             return;
451         }
452
453         session = new SocketChannelSession(getThing().getUID().getAsString(), config.getIpAddress(), 23);
454         atlonaHandler = new AtlonaPro3PortocolHandler(session, config, getCapabilities(),
455                 new StatefulHandlerCallback(new AtlonaHandlerCallback() {
456                     @Override
457                     public void stateChanged(String channelId, State state) {
458                         updateState(channelId, state);
459                     }
460
461                     @Override
462                     public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
463                         updateStatus(status, detail, msg);
464
465                         if (status != ThingStatus.ONLINE) {
466                             disconnect(true);
467                         }
468                     }
469
470                     @Override
471                     public void setProperty(String propertyName, String propertyValue) {
472                         getThing().setProperty(propertyName, propertyValue);
473                     }
474                 }));
475
476         // Try initial connection in a scheduled task
477         this.scheduler.schedule(this::connect, 1, TimeUnit.SECONDS);
478     }
479
480     /**
481      * Attempts to connect to the switch. If successfully connect, the {@link AtlonaPro3PortocolHandler#login()} will be
482      * called to log into the switch (if needed). Once completed, a polling job will be created to poll the switch's
483      * actual state and a ping job to ping the server. If a connection cannot be established (or login failed), the
484      * connection attempt will be retried later (via {@link #retryConnect()})
485      */
486     private void connect() {
487         String response = "Server is offline - will try to reconnect later";
488         try {
489             // clear listeners to avoid any 'old' listener from handling initial messages
490             session.clearListeners();
491             session.connect();
492
493             if (this.getCapabilities().isUHDModel()) {
494                 response = atlonaHandler.loginUHD();
495             } else {
496                 response = atlonaHandler.loginHD();
497             }
498
499             if (response == null) {
500                 final AtlonaPro3Config config = getAtlonaConfig();
501                 if (config != null) {
502                     polling = this.scheduler.scheduleWithFixedDelay(() -> {
503                         final ThingStatus status = getThing().getStatus();
504                         if (status == ThingStatus.ONLINE) {
505                             if (session.isConnected()) {
506                                 atlonaHandler.refreshAll();
507                             } else {
508                                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
509                                         "Atlona PRO3 has disconnected. Will try to reconnect later.");
510                             }
511                         } else if (status == ThingStatus.OFFLINE) {
512                             disconnect(true);
513                         }
514                     }, config.getPolling(), config.getPolling(), TimeUnit.SECONDS);
515
516                     ping = this.scheduler.scheduleWithFixedDelay(() -> {
517                         final ThingStatus status = getThing().getStatus();
518                         if (status == ThingStatus.ONLINE) {
519                             if (session.isConnected()) {
520                                 atlonaHandler.ping();
521                             }
522                         }
523                     }, config.getPing(), config.getPing(), TimeUnit.SECONDS);
524
525                     updateStatus(ThingStatus.ONLINE);
526                     return;
527                 }
528             }
529
530         } catch (Exception e) {
531             // do nothing
532         }
533
534         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, response);
535         retryConnect();
536     }
537
538     /**
539      * Attempts to disconnect from the session and will optionally retry the connection attempt. The {@link #polling}
540      * will be cancelled, the {@link #ping} will be cancelled and both set to null then the {@link #session} will be
541      * disconnected.
542      *
543      * @param retryConnection true to retry connection attempts after the disconnect
544      */
545     private void disconnect(boolean retryConnection) {
546         // Cancel polling
547         if (polling != null) {
548             polling.cancel(true);
549             polling = null;
550         }
551
552         // Cancel ping
553         if (ping != null) {
554             ping.cancel(true);
555             ping = null;
556         }
557
558         if (session != null) {
559             try {
560                 session.disconnect();
561             } catch (IOException e) {
562                 // ignore - we don't care
563             }
564         }
565
566         if (retryConnection) {
567             retryConnect();
568         }
569     }
570
571     /**
572      * Retries the connection attempt - schedules a job in {@link AtlonaPro3Config#getRetryPolling()} seconds to call
573      * the
574      * {@link #connect()} method. If a retry attempt is pending, the request is ignored.
575      */
576     private void retryConnect() {
577         if (retryConnection == null) {
578             final AtlonaPro3Config config = getAtlonaConfig();
579             if (config != null) {
580                 logger.info("Will try to reconnect in {} seconds", config.getRetryPolling());
581                 retryConnection = this.scheduler.schedule(() -> {
582                     retryConnection = null;
583                     connect();
584                 }, config.getRetryPolling(), TimeUnit.SECONDS);
585             }
586         } else {
587             logger.debug("RetryConnection called when a retry connection is pending - ignoring request");
588         }
589     }
590
591     /**
592      * Simple gets the {@link AtlonaPro3Config} from the {@link Thing} and will set the status to offline if not found.
593      *
594      * @return {@link AtlonaPro3Config}
595      */
596     private AtlonaPro3Config getAtlonaConfig() {
597         return getThing().getConfiguration().as(AtlonaPro3Config.class);
598     }
599
600     /**
601      * {@inheritDoc}
602      *
603      * Disposes of the handler. Will simply call {@link #disconnect(boolean)} to disconnect and NOT retry the
604      * connection
605      */
606     @Override
607     public void dispose() {
608         disconnect(false);
609     }
610 }