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