ardop_interface/tnc/
ardoptnc.rs

1//! Asynchronous ARDOP TNC Interface
2
3use std::convert::Into;
4use std::fmt;
5use std::net::SocketAddr;
6use std::string::String;
7use std::sync::Arc;
8use std::time::Duration;
9
10use async_std::prelude::FutureExt;
11use futures::lock::Mutex;
12
13use super::{DiscoveredPeer, PingAck, PingFailedReason, TncResult};
14
15use crate::arq::{ArqStream, ConnectionFailedReason};
16use crate::protocol::command;
17use crate::protocol::command::Command;
18use crate::tncio::asynctnc::{AsyncTncTcp, ConnectionInfoOrPeerDiscovery};
19
20/// TNC Interface
21///
22/// See the [module-level](index.html) documentation
23/// for examples and usage details.
24pub struct ArdopTnc {
25    inner: Arc<Mutex<AsyncTncTcp>>,
26    mycall: String,
27    clear_time: Duration,
28    clear_max_wait: Duration,
29}
30
31/// Result of [`ArdopTnc::listen_monitor()`](../tnc/struct.ArdopTnc.html#method.listen_monitor)
32///
33/// This value indicates either the
34/// * completion of an inbound ARQ connection; or
35/// * discovery of a remote peer, via its transmissions
36pub enum ListenMonitor {
37    /// ARQ Connection
38    Connection(ArqStream),
39
40    /// Heard callsign
41    PeerDiscovery(DiscoveredPeer),
42}
43
44impl ArdopTnc {
45    /// Connect to an ARDOP TNC
46    ///
47    /// Returns a future which will connect to an ARDOP TNC
48    /// and initialize it.
49    ///
50    /// # Parameters
51    /// - `addr`: Network address of the ARDOP TNC's control port.
52    /// - `mycall`: The formally-assigned callsign for your station.
53    ///   Legitimate call signs include from 3 to 7 ASCII characters
54    ///   (A-Z, 0-9) followed by an optional "`-`" and an SSID of
55    ///   `-0` to `-15` or `-A` to `-Z`. An SSID of `-0` is treated
56    ///   as no SSID.
57    ///
58    /// # Returns
59    /// A new `ArdopTnc`, or an error if the connection or
60    /// initialization step fails.
61    pub async fn new<S>(addr: &SocketAddr, mycall: S) -> TncResult<Self>
62    where
63        S: Into<String>,
64    {
65        let mycall = mycall.into();
66        let mycall2 = mycall.clone();
67
68        let tnc = AsyncTncTcp::new(addr, mycall2).await?;
69        let inner = Arc::new(Mutex::new(tnc));
70        Ok(ArdopTnc {
71            inner,
72            mycall,
73            clear_time: DEFAULT_CLEAR_TIME,
74            clear_max_wait: DEFAULT_CLEAR_MAX_WAIT,
75        })
76    }
77
78    /// Get this station's callsign
79    ///
80    /// # Returns
81    /// The formally assigned callsign for this station.
82    pub fn mycall(&self) -> &String {
83        &self.mycall
84    }
85
86    /// Returns configured clear time
87    ///
88    /// The *clear time* is the duration that the RF channel must
89    /// be sensed as clear before an outgoing transmission will be
90    /// allowed to proceed.
91    ///
92    /// See [`set_clear_time()`](#method.set_clear_time).
93    pub fn clear_time(&self) -> &Duration {
94        &self.clear_time
95    }
96
97    /// Returns configured maximum clear waiting time
98    ///
99    /// Caps the maximum amount of time that a transmitting
100    /// Future, such as [`connect()`](#method.connect) or
101    /// [`ping()`](#method.ping), will spend waiting for the
102    /// channel to become clear.
103    ///
104    /// If the channel does not become clear within this
105    /// Duration, then a `TimedOut` error is raised.
106    ///
107    /// See [`set_clear_max_wait()`](#method.set_clear_max_wait).
108    pub fn clear_max_wait(&self) -> &Duration {
109        &self.clear_max_wait
110    }
111
112    /// Returns configured clear time
113    ///
114    /// The *clear time* is the duration that the RF channel must
115    /// be sensed as clear before an outgoing transmission will be
116    /// allowed to proceed.
117    ///
118    /// You should **ALWAYS** allow the busy-detection logic ample
119    /// time to declare that the channel is clear of any other
120    /// communications. The busy detector's sensitivity may be adjusted
121    /// via [`set_busydet()`](#method.set_busydet) if it is too sensitive
122    /// for your RF environment.
123    ///
124    /// If you need to send a *DISTRESS* or *EMERGENCY* signal, you may
125    /// set `tm` to zero to disable the busy-detection logic entirely.
126    ///
127    /// # Parameters
128    /// - `tm`: New clear time.
129    pub fn set_clear_time(&mut self, tm: Duration) {
130        self.clear_time = tm;
131    }
132
133    /// Returns configured maximum clear waiting time
134    ///
135    /// Caps the maximum amount of time that a transmitting
136    /// Future, such as [`connect()`](#method.connect) or
137    /// [`ping()`](#method.ping), will spend waiting for the
138    /// channel to become clear.
139    ///
140    /// If the channel does not become clear within this
141    /// Duration, then a `TimedOut` error is raised.
142    ///
143    /// # Parameters
144    /// - `tm`: New timeout for clear-channel waiting. `tm`
145    ///   must be at least the
146    ///   [`clear_time()`](#method.clear_time).
147    pub fn set_clear_max_wait(&mut self, tm: Duration) {
148        self.clear_max_wait = tm;
149    }
150
151    /// Ping a remote `target` peer
152    ///
153    /// When run, this future will
154    ///
155    /// 1. Wait for a clear channel
156    /// 2. Send an outgoing `PING` request
157    /// 3. Wait for a reply or for the ping timeout to elapse
158    /// 4. Complete with the ping result
159    ///
160    /// # Parameters
161    /// - `target`: Peer callsign, with optional `-SSID` portion
162    /// - `attempts`: Number of ping packets to send before
163    ///   giving up
164    ///
165    /// # Return
166    /// The outer result contains failures related to the local
167    /// TNC connection.
168    ///
169    /// The inner result is the success or failure of the round-trip
170    /// ping. If the ping succeeds, returns an `Ok(PingAck)`
171    /// with the response from the remote peer. If the ping fails,
172    /// returns `Err(PingFailedReason)`. Errors include:
173    ///
174    /// * `Busy`: The RF channel was busy during the ping attempt,
175    ///           and no ping was sent.
176    /// * `NoAnswer`: The remote peer did not answer.
177    pub async fn ping<S>(
178        &mut self,
179        target: S,
180        attempts: u16,
181    ) -> TncResult<Result<PingAck, PingFailedReason>>
182    where
183        S: Into<String>,
184    {
185        let mut tnc = self.inner.lock().await;
186        tnc.ping(
187            target,
188            attempts,
189            self.clear_time.clone(),
190            self.clear_max_wait.clone(),
191        )
192        .await
193    }
194
195    /// Dial a remote `target` peer
196    ///
197    /// When run, this future will
198    ///
199    /// 1. Wait for a clear channel.
200    /// 2. Make an outgoing `ARQCALL` to the designated callsign
201    /// 3. Wait for a connection to either complete or fail
202    /// 4. Successful connections will return an
203    ///    [`ArqStream`](../arq/struct.ArqStream.html).
204    ///
205    /// # Parameters
206    /// - `target`: Peer callsign, with optional `-SSID` portion
207    /// - `bw`: ARQ bandwidth to use
208    /// - `bw_forced`: If false, will potentially negotiate for a
209    ///   *lower* bandwidth than `bw` with the remote peer. If
210    ///   true, the connection will be made at `bw` rate---or not
211    ///   at all.
212    /// - `attempts`: Number of connection attempts to make
213    ///   before giving up
214    ///
215    /// # Return
216    /// The outer result contains failures related to the local
217    /// TNC connection.
218    ///
219    /// The inner result contains failures related to the RF
220    /// connection. If the connection attempt succeeds, returns
221    /// a new [`ArqStream`](../arq/struct.ArqStream.html) that
222    /// can be used like an asynchronous `TcpStream`.
223    pub async fn connect<S>(
224        &mut self,
225        target: S,
226        bw: u16,
227        bw_forced: bool,
228        attempts: u16,
229    ) -> TncResult<Result<ArqStream, ConnectionFailedReason>>
230    where
231        S: Into<String>,
232    {
233        let mut tnc = self.inner.lock().await;
234        match tnc
235            .connect(
236                target,
237                bw,
238                bw_forced,
239                attempts,
240                self.clear_time.clone(),
241                self.clear_max_wait.clone(),
242            )
243            .await?
244        {
245            Ok(nfo) => Ok(Ok(ArqStream::new(self.inner.clone(), nfo))),
246            Err(e) => Ok(Err(e)),
247        }
248    }
249
250    /// Listen for incoming connections
251    ///
252    /// When run, this future will wait for the TNC to accept
253    /// an incoming connection to `MYCALL` or one of `MYAUX`.
254    /// When a connection is accepted, the future will resolve
255    /// to an [`ArqStream`](../arq/struct.ArqStream.html).
256    ///
257    /// # Parameters
258    /// - `bw`: Maximum ARQ bandwidth to use
259    /// - `bw_forced`: If false, will potentially negotiate for a
260    ///   *lower* bandwidth than `bw` with the remote peer. If
261    ///   true, the connection will be made at `bw` rate---or not
262    ///   at all.
263    /// - `timeout`: Return a `TimedOut` error if no incoming
264    ///   connection is received within the `timeout`. A timeout of
265    ///   "zero" will wait forever.
266    ///
267    /// # Return
268    /// The result contains failures related to the local TNC
269    /// connection.
270    ///
271    /// Connections which fail during the setup phase
272    /// will not be reported to the application. Unless the local
273    /// TNC fails, or the timeout elapses, this method will not fail.
274    ///
275    /// An expired timeout will return a
276    /// [`TncError::TimedOut`](enum.TncError.html#variant.TimedOut).
277    pub async fn listen(
278        &mut self,
279        bw: u16,
280        bw_forced: bool,
281        timeout: Duration,
282    ) -> TncResult<ArqStream> {
283        let mut tnc = self.inner.lock().await;
284        let nfo = if timeout > Duration::from_nanos(0) {
285            tnc.listen(bw, bw_forced).timeout(timeout).await??
286        } else {
287            tnc.listen(bw, bw_forced).await?
288        };
289        Ok(ArqStream::new(self.inner.clone(), nfo))
290    }
291
292    /// Passively monitor for band activity
293    ///
294    /// When run, the TNC will listen passively for peers which
295    /// announce themselves via:
296    ///
297    /// * ID Frames (`IDF`)
298    /// * Pings
299    ///
300    /// The TNC has no memory of discovered stations and will
301    /// return a result every time it hears one.
302    ///
303    /// # Parameters
304    /// - `timeout`: Return a `TimedOut` error if no activity is
305    ///   detected within the `timeout`. A timeout of
306    ///   "zero" will wait forever.
307    ///
308    /// # Return
309    /// The result contains failures related to the local TNC
310    /// connection.
311    ///
312    /// Unless the local TNC fails, or the timeout elapses, this method
313    /// will not fail. If a peer is discovered, a
314    /// [`DiscoveredPeer`](struct.DiscoveredPeer.html)
315    /// is returned.
316    pub async fn monitor(&mut self, timeout: Duration) -> TncResult<DiscoveredPeer> {
317        let mut tnc = self.inner.lock().await;
318        if timeout > Duration::from_nanos(0) {
319            tnc.monitor().timeout(timeout).await?
320        } else {
321            tnc.monitor().await
322        }
323    }
324
325    /// Listen for incoming connections or for band activity
326    ///
327    /// This method combines [`listen()`](#method.listen) and
328    /// [`monitor()`](#method.monitor). The TNC will listen for
329    /// the next inbound ARQ connection OR for band activity and
330    /// return the first it finds.
331    ///
332    /// The incoming connection may be directed at either `MYCALL`
333    /// or any of `MYAUX`.
334    ///
335    /// Band activity will be reported from any available source.
336    /// At present, these sources are available:
337    ///
338    /// * ID Frames
339    /// * Ping requests
340    ///
341    /// # Parameters
342    /// - `bw`: Maximum ARQ bandwidth to use. Only applies to
343    ///   incoming connections—not peer discoveries.
344    /// - `bw_forced`: If false, will potentially negotiate for a
345    ///   *lower* bandwidth than `bw` with the remote peer. If
346    ///   true, the connection will be made at `bw` rate---or not
347    ///   at all.
348    /// - `timeout`: Return a `TimedOut` error if no activity or
349    ///   connection is detected within the `timeout`. A timeout of
350    ///   "zero" will wait forever.
351    ///
352    /// # Return
353    /// The result contains failures related to the local TNC
354    /// connection. The result will also error if the timeout
355    /// expires.
356    ///
357    /// Connections which fail during the setup phase
358    /// will not be reported to the application.
359    pub async fn listen_monitor(
360        &mut self,
361        bw: u16,
362        bw_forced: bool,
363        timeout: Duration,
364    ) -> TncResult<ListenMonitor> {
365        let mut tnc = self.inner.lock().await;
366        let res = if timeout > Duration::from_nanos(0) {
367            tnc.listen_monitor(bw, bw_forced).timeout(timeout).await??
368        } else {
369            tnc.listen_monitor(bw, bw_forced).await?
370        };
371
372        match res {
373            ConnectionInfoOrPeerDiscovery::Connection(nfo) => Ok(ListenMonitor::Connection(
374                ArqStream::new(self.inner.clone(), nfo),
375            )),
376            ConnectionInfoOrPeerDiscovery::PeerDiscovery(peer) => {
377                Ok(ListenMonitor::PeerDiscovery(peer))
378            }
379        }
380    }
381
382    /// Send ID frame
383    ///
384    /// Sends an ID frame immediately, followed by a CW ID
385    /// (if `ArdopTnc::set_cwid()` is set)
386    ///
387    /// Completion of this future does not indicate that the
388    /// ID frame has actually been completely transmitted.
389    ///
390    /// # Return
391    /// An empty if an ID frame was/will be sent, or some `TncError`
392    /// if an ID frame will not be sent.
393    pub async fn sendid(&mut self) -> TncResult<()> {
394        self.command(command::sendid()).await?;
395        info!("Transmitting ID frame: {}", self.mycall);
396        Ok(())
397    }
398
399    /// Start a two-tone test
400    ///
401    /// Send 5 second two-tone burst, at the normal leader
402    /// amplitude. May be used in adjusting drive level to
403    /// the radio.
404    ///
405    /// Completion of this future does not indicate that the
406    /// two-tone test has actually been completed.
407    ///
408    /// # Return
409    /// An empty if an two-tone test sequence be sent, or some
410    /// `TncError` if the test cannot be performed.
411    pub async fn twotonetest(&mut self) -> TncResult<()> {
412        self.command(command::twotonetest()).await?;
413        info!("Transmitting two-tone test");
414        Ok(())
415    }
416
417    /// Set ARQ connection timeout
418    ///
419    /// Set the ARQ Timeout in seconds. If no data has flowed in the
420    /// channel in `timeout` seconds, the link is declared dead. A `DISC`
421    /// command is sent and a reset to the `DISC` state is initiated.
422    ///
423    /// If either end of the ARQ session hits it’s `ARQTIMEOUT` without
424    /// data flow the link will automatically be terminated.
425    ///
426    /// # Parameters
427    /// - `timeout`: ARQ timeout period, in seconds (30 -- 600)
428    ///
429    /// # Return
430    /// An empty on success or a `TncError` if the command could not
431    /// be completed for any reason, including timeouts.
432    pub async fn set_arqtimeout(&mut self, timeout: u16) -> TncResult<()> {
433        self.command(command::arqtimeout(timeout)).await
434    }
435
436    /// Busy detector threshold value
437    ///
438    /// Sets the current Busy detector threshold value (default = 5). The
439    /// default value should be sufficient for most installations. Lower
440    /// values will make the busy detector more sensitive; the channel will
441    /// be declared busy *more frequently*. Higher values may be used for
442    /// high-noise environments.
443    ///
444    /// # Parameters
445    /// - `level`: Busy detector threshold (0 -- 10). A value of 0 will disable
446    ///   the busy detector (not recommended).
447    ///
448    /// # Return
449    /// An empty on success or a `TncError` if the command could not
450    /// be completed for any reason, including timeouts.
451    pub async fn set_busydet(&mut self, level: u16) -> TncResult<()> {
452        self.command(command::busydet(level)).await
453    }
454
455    /// Send CW after ID frames
456    ///
457    /// Set to true to send your callsign in morse code (CW), as station ID,
458    /// at the end of every ID frame. In many regions, a CW ID is always
459    /// sufficient to meet station ID requirements. Some regions may
460    /// require it.
461    ///
462    /// # Parameters
463    /// - `cw`: Send CW ID with ARDOP digital ID frames
464    ///
465    /// # Return
466    /// An empty on success or a `TncError` if the command could not
467    /// be completed for any reason, including timeouts.
468    pub async fn set_cwid(&mut self, set_cwid: bool) -> TncResult<()> {
469        self.command(command::cwid(set_cwid)).await
470    }
471
472    /// Set your station's grid square
473    ///
474    /// Sets the 4, 6, or 8-character Maidenhead Grid Square for your
475    /// station. A correct grid square is useful for studying and
476    /// logging RF propagation-and for bragging rights.
477    ///
478    /// Your grid square will be sent in ID frames.
479    ///
480    /// # Parameters
481    /// - `grid`: Your grid square (4, 6, or 8-characters).
482    ///
483    /// # Return
484    /// An empty on success or a `TncError` if the command could not
485    /// be completed for any reason, including timeouts.
486    pub async fn set_gridsquare<S>(&mut self, grid: S) -> TncResult<()>
487    where
488        S: Into<String>,
489    {
490        self.command(command::gridsquare(grid)).await
491    }
492
493    /// Leader tone duration
494    ///
495    /// Sets the leader length in ms. (Default is 160 ms). Rounded to
496    /// the nearest 20 ms. Note for VOX keying or some SDR radios the
497    /// leader may have to be extended for reliable decoding.
498    ///
499    /// # Parameters
500    /// - `duration`: Leader tone duration, milliseconds
501    ///
502    /// # Return
503    /// An empty on success or a `TncError` if the command could not
504    /// be completed for any reason, including timeouts.
505    pub async fn set_leader(&mut self, duration: u16) -> TncResult<()> {
506        self.command(command::leader(duration)).await
507    }
508
509    /// Set your station's auxiliary callsigns
510    ///
511    /// `MYAUX` is only used for `LISTEN`ing, and it will not be used for
512    /// connect requests.
513    ///
514    /// Legitimate call signs include from 3 to 7 ASCII characters (A-Z, 0-9)
515    /// followed by an optional "`-`" and an SSID of `-0` to `-15` or `-A`
516    /// to `-Z`. An SSID of `-0` is treated as no SSID.
517    ///
518    /// # Parameters:
519    /// - `aux`: Vector of auxiliary callsigns. If empty, all aux callsigns
520    ///   will be removed.
521    ///
522    /// # Return
523    /// An empty on success or a `TncError` if the command could not
524    /// be completed for any reason, including timeouts.
525    pub async fn set_myaux(&mut self, aux: Vec<String>) -> TncResult<()> {
526        self.command(command::myaux(aux)).await
527    }
528
529    /// Query TNC version
530    ///
531    /// Queries the ARDOP TNC software for its version number.
532    /// The format of the version number is unspecified and may
533    /// be empty.
534    ///
535    /// # Return
536    /// Version string, or an error if the version string could
537    /// not be retrieved.
538    pub async fn version(&mut self) -> TncResult<String> {
539        let mut tnc = self.inner.lock().await;
540        tnc.version().await
541    }
542
543    /// Gets the control connection timeout value
544    ///
545    /// Commands sent via the `command()` method will
546    /// timeout if either the send or receive takes
547    /// longer than `timeout`.
548    ///
549    /// Timeouts cause `TncError::IoError`s of kind
550    /// `io::ErrorKind::TimedOut`. Control timeouts
551    /// usually indicate a serious problem with the ARDOP
552    /// TNC or its connection.
553    ///
554    /// # Returns
555    /// Current timeout value
556    pub async fn control_timeout(&self) -> Duration {
557        self.inner.lock().await.control_timeout().clone()
558    }
559
560    /// Sets timeout for the control connection
561    ///
562    /// Commands sent via the `command()` method will
563    /// timeout if either the send or receive takes
564    /// longer than `timeout`.
565    ///
566    /// Timeouts cause `TncError::IoError`s of kind
567    /// `io::ErrorKind::TimedOut`. Control timeouts
568    /// usually indicate a serious problem with the ARDOP
569    /// TNC or its connection.
570    ///
571    /// # Parameters
572    /// - `timeout`: New command timeout value
573    pub async fn set_control_timeout(&mut self, timeout: Duration) {
574        let mut tnc = self.inner.lock().await;
575        tnc.set_control_timeout(timeout)
576    }
577
578    // Send a command to the TNC and await the response
579    //
580    // A future which will send the given command and wait
581    // for a success or failure response. The waiting time
582    // is upper-bounded by AsyncTnc's `control_timeout()`
583    // value.
584    //
585    // # Parameters
586    // - `cmd`: The Command to send
587    //
588    // # Returns
589    // An empty on success or a `TncError`.
590    async fn command<F>(&mut self, cmd: Command<F>) -> TncResult<()>
591    where
592        F: fmt::Display,
593    {
594        let mut tnc = self.inner.lock().await;
595        tnc.command(cmd).await
596    }
597}
598
599const DEFAULT_CLEAR_TIME: Duration = Duration::from_secs(10);
600const DEFAULT_CLEAR_MAX_WAIT: Duration = Duration::from_secs(90);