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);