View Javadoc
1   /*
2    * Copyright © 2016 Greg Chabala
3    *
4    * This file is part of brick-control-lab.
5    *
6    * brick-control-lab is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as
8    * published by the Free Software Foundation, either version 3 of the
9    * License, or (at your option) any later version.
10   *
11   * brick-control-lab is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public License
17   * along with brick-control-lab.  If not, see http://www.gnu.org/licenses/.
18   */
19  package org.chabala.brick.controllab;
20  
21  import org.slf4j.Logger;
22  import org.slf4j.LoggerFactory;
23  
24  import java.io.Closeable;
25  import java.io.IOException;
26  import java.time.Duration;
27  import java.util.concurrent.*;
28  
29  /**
30   * Handles sending keep alive messages in the absence of other commands.
31   */
32  class KeepAliveMonitor implements Closeable {
33      private final Logger log = LoggerFactory.getLogger(getClass());
34      private final SerialPortWriter serialPortWriter;
35      private final ScheduledExecutorService executor;
36      private final long keepAlivePeriodMs;
37      private static final byte KEEP_ALIVE_COMMAND = 0x02;
38      /** Slightly less than two seconds. */
39      private static final int KEEP_ALIVE_PERIOD_MS = 1800;
40      private ScheduledFuture<?> task;
41  
42      /**
43       * Creates a keep alive monitor for the serial port managed by the specified
44       * writer. As soon as it's constructed, will send a keep alive message to the
45       * serial port with a frequency just under two seconds, as long as the port
46       * is open.
47       * @param serialPortWriter the port writer to receive keep alive messages
48       */
49      KeepAliveMonitor(SerialPortWriter serialPortWriter) {
50          this(serialPortWriter, Duration.ofMillis(KEEP_ALIVE_PERIOD_MS));
51      }
52  
53      KeepAliveMonitor(SerialPortWriter serialPortWriter, Duration keepAlivePeriod) {
54          this.serialPortWriter = serialPortWriter;
55          this.keepAlivePeriodMs = keepAlivePeriod.toMillis();
56          executor = Executors.newSingleThreadScheduledExecutor(
57                  new NamedDaemonThreadFactory(
58                          "KeepAlive " + serialPortWriter.getPortName()));
59          scheduleTask();
60      }
61  
62      /**
63       * Call this method when a command is being sent, to reset the countdown
64       * on the keep alive monitor. It will reschedule the next keep alive
65       * message from this moment.
66       */
67      void reset() {
68          task.cancel(false);
69          scheduleTask();
70      }
71  
72      private void scheduleTask() {
73          task = executor.scheduleAtFixedRate(() -> sendCommand(KEEP_ALIVE_COMMAND),
74                  keepAlivePeriodMs, keepAlivePeriodMs, TimeUnit.MILLISECONDS);
75      }
76  
77      private void sendCommand(byte b) {
78          try {
79              serialPortWriter.sendCommand(b, log);
80          } catch (IOException e) {
81              log.error(e.getMessage(), e);
82          }
83      }
84  
85      /**
86       * Cancel the next keep alive message and shutdown the monitor.
87       */
88      @Override
89      public void close() {
90          if (executor != null) {
91              executor.shutdownNow();
92          }
93      }
94  
95      /**
96       * A {@link ThreadFactory} which produces named, daemon threads.
97       */
98      private static class NamedDaemonThreadFactory implements ThreadFactory {
99          private ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();
100         private final String threadName;
101 
102         NamedDaemonThreadFactory(String threadName) {
103             this.threadName = threadName;
104         }
105 
106         @Override
107         public Thread newThread(Runnable r) {
108             Thread thread = defaultThreadFactory.newThread(r);
109             thread.setName(threadName);
110             thread.setDaemon(true);
111             return thread;
112         }
113     }
114 }