Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ The crate `ax25_tnc` provides:
Most developers will want to focus on `tnc::TncAddress` and `tnc::Tnc`.
1. Generate or ask the user to supply an address string. This takes the form:
`tnc:tcpkiss:192.168.0.1:8001` or
`tnc:linuxif:vk7ntk-2`
`tnc:linuxif:vk7ntk-2` or
`tnc:serialkiss:/dev/ttyUSB0:9600`
2. Parse this to an address: `let addr = string.parse::<TncAddress>()?;`
3. Attempt to open the TNC: `let tnc = Tnc::open(&addr)?;`
4. Use `send_frame()` and `receive_frame()` to communicate on the radio.
Expand All @@ -56,6 +57,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("where tnc-address is something like");
println!(" tnc:linuxif:vk7ntk-2");
println!(" tnc:tcpkiss:192.168.0.1:8001");
println!(" tnc:serialkiss:/dev/ttyUSB0:9600");
std::process::exit(1);
}

Expand Down Expand Up @@ -89,7 +91,6 @@ is available through its fields which are not printed here.

Planned features:

* Support for serial KISS TNCs (physical, TNC-Pi, Dire Wolf pseudo-tty)
* Paclen management
* More convenient send/receive interfaces for messing around with UI frames
* Direct use of linux axports interfaces without `kissattach`
4 changes: 4 additions & 0 deletions ax25_tnc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ readme = "../README.md"
ax25 = "0.3"
#ax25 = { path = "../ax25" }
libc = "0.2"
serialport = { version = "4.9.0", optional = true }

[dev-dependencies]
time = { version = "0.3.9", features = ["local-offset"] }

[features]
serial = ["dep:serialport"]
1 change: 1 addition & 0 deletions ax25_tnc/examples/listen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("where tnc-address is something like");
println!(" tnc:linuxif:vk7ntk-2");
println!(" tnc:tcpkiss:192.168.0.1:8001");
println!(" tnc:serialkiss:/dev/ttyUSB0:9600");
std::process::exit(1);
}

Expand Down
115 changes: 115 additions & 0 deletions ax25_tnc/src/kiss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ use std::net::ToSocketAddrs;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;

#[cfg(feature = "serial")]
use serialport::{self, SerialPort};

#[cfg(feature = "serial")]
use std::time::Duration;

#[cfg(not(feature = "serial"))]
use std::io::{Error, ErrorKind};

const FEND: u8 = 0xC0;
const FESC: u8 = 0xDB;
const TFEND: u8 = 0xDC;
Expand Down Expand Up @@ -79,6 +88,112 @@ impl Drop for TcpKissInterface {
}
}

pub(crate) struct SerialKissInterface {
// Interior mutability is desirable so that we can clone the TNC and have
// different threads sending and receiving concurrently.
#[cfg(feature = "serial")]
tx_stream: Mutex<Box<dyn SerialPort>>,
#[cfg(feature = "serial")]
rx_stream: Mutex<Box<dyn SerialPort>>,
#[cfg(feature = "serial")]
buffer: Mutex<Vec<u8>>,
is_shutdown: AtomicBool,
}

impl SerialKissInterface {
#[allow(unused_variables)]
pub(crate) fn new(device: &str, baud: u32) -> io::Result<SerialKissInterface> {
#[cfg(feature = "serial")]
{
let tx_stream = serialport::new(device, baud)
.timeout(Duration::MAX)
.open()?;
let rx_stream = tx_stream.try_clone()?;
Ok(SerialKissInterface {
tx_stream: Mutex::new(tx_stream),
rx_stream: Mutex::new(rx_stream),
buffer: Mutex::new(Vec::new()),
is_shutdown: AtomicBool::new(false),
})
}

#[cfg(not(feature = "serial"))]
{
Err(Error::new(
ErrorKind::Unsupported,
"Serial port devices are not supported.",
))
}
}

pub(crate) fn receive_frame(&self) -> io::Result<Vec<u8>> {
#[cfg(feature = "serial")]
{
loop {
{
let mut buffer = self.buffer.lock().unwrap();
if let Some(frame) = make_frame_from_buffer(&mut buffer) {
return Ok(frame);
}
}
let mut buf = vec![0u8; 1024];
let n_bytes = {
let mut rx_stream = self.rx_stream.lock().unwrap();
rx_stream.read(&mut buf)?
};
{
let mut buffer = self.buffer.lock().unwrap();
buffer.extend(buf.iter().take(n_bytes));
}
}
}

#[cfg(not(feature = "serial"))]
{
Err(Error::new(
ErrorKind::NotConnected,
"Serial port devices are not supported.",
))
}
}

#[allow(unused_variables)]
pub(crate) fn send_frame(&self, frame: &[u8]) -> io::Result<()> {
#[cfg(feature = "serial")]
{
let mut tx_stream = self.tx_stream.lock().unwrap();
// 0x00 is the KISS command byte, which is two nybbles
// port = 0
// command = 0 (all following bytes are a data frame to transmit)
tx_stream.write_all(&[FEND, 0x00])?;
tx_stream.write_all(frame)?;
tx_stream.write_all(&[FEND])?;
tx_stream.flush()?;
Ok(())
}

#[cfg(not(feature = "serial"))]
{
Err(Error::new(
ErrorKind::NotConnected,
"Serial port devices are not supported.",
))
}
}

pub(crate) fn shutdown(&self) {
if !self.is_shutdown.load(Ordering::SeqCst) {
self.is_shutdown.store(true, Ordering::SeqCst);
}
}
}

impl Drop for SerialKissInterface {
fn drop(&mut self) {
self.shutdown();
}
}

fn make_frame_from_buffer(buffer: &mut Vec<u8>) -> Option<Vec<u8>> {
let mut possible_frame = Vec::new();

Expand Down
5 changes: 1 addition & 4 deletions ax25_tnc/src/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,7 @@ mod sys {
if req.data.address_family() as i32 != AF_AX25 {
return None;
}
let hw_addr = match req.data.ax25_address() {
Some(addr) => addr,
None => return None,
};
let hw_addr = req.data.ax25_address()?;

if unsafe { ioctl(fd, SIOCGIFINDEX, &mut req) } == -1 {
return None;
Expand Down
Loading