-use crate::kiss::{KissBuffer, KissFrame, PORT_PACKET_BASIC, PORT_PACKET_FULL, PORT_STREAM};
+use crate::address::{Address, Callsign};
+use crate::kiss::{
+ KissBuffer, KissCommand, KissFrame, PORT_PACKET_BASIC, PORT_PACKET_FULL, PORT_STREAM,
+};
use crate::modem::ModulatorFrame;
use crate::protocol::{
Frame, LichCollection, LsfFrame, Mode, PacketFrame, PacketFrameCounter, StreamFrame,
/// Latest state of data carrier detect from demodulator - controls whether we can go to TX
dcd: bool,
+ /// If CSMA declined to transmit into an idle slot, at what point do we next check it?
+ next_csma_check: Option<u64>,
+
/// Current monotonic time, counted in samples
now: u64,
/// Should PTT be on right now? Polled by external
ptt: bool,
+
+ /// TxDelay raw value, number of 10ms units. We will optimistically start with default 0.
+ tx_delay: u8,
+
+ /// This is a full duplex channel so we do not need to monitor DCD or use CSMA. Default false.
+ full_duplex: bool,
}
impl SoftTnc {
outgoing_kiss: None,
state: State::Idle,
dcd: false,
+ next_csma_check: None,
now: 0,
packet_queue: Default::default(),
packet_next: 0,
stream_curr: 0,
stream_full: false,
ptt: false,
+ tx_delay: 0,
+ full_duplex: false,
}
}
/// Process an individual `Frame` that has been decoded by the modem.
pub fn handle_frame(&mut self, frame: Frame) {
+ if self.ptt {
+ // Ignore self-decodes
+ return;
+ }
match frame {
Frame::Lsf(lsf) => {
// A new LSF implies a clean slate.
Mode::Stream => {
let kiss = KissFrame::new_stream_setup(&lsf.0).unwrap();
self.kiss_to_host(kiss);
- self.state = State::RxStream(RxStreamState { lsf, index: 0 });
+ self.state = State::RxStream(RxStreamState {
+ _lsf: lsf,
+ index: 0,
+ });
}
}
}
let start = 25 * rx.count;
let end = start + payload_len;
rx.packet[start..(start + payload_len)]
- .copy_from_slice(&packet.payload);
+ .copy_from_slice(&packet.payload[0..payload_len]);
// TODO: compatible packets should be sent on port 0 too
let kiss =
KissFrame::new_full_packet(&rx.lsf.0, &rx.packet[0..end])
// TODO: avoid discarding the first data payload here
// need a queue depth of 2 for outgoing kiss
self.state = State::RxStream(RxStreamState {
- lsf,
+ _lsf: lsf,
index: stream.frame_number + 1,
});
}
pub fn set_now(&mut self, now_samples: u64) {
self.now = now_samples;
- match self.state {
- State::TxEndingAtTime(time) => {
- if now_samples >= time {
- self.ptt = false;
- self.state = State::Idle;
- }
+ if let State::TxEndingAtTime(time) = self.state {
+ if now_samples >= time {
+ self.ptt = false;
+ self.state = State::Idle;
}
- _ => (),
}
}
}
pub fn set_tx_end_time(&mut self, in_samples: usize) {
- match self.state {
- State::TxEnding => {
- self.state = State::TxEndingAtTime(self.now + in_samples as u64);
- }
- _ => (),
+ log::debug!("tnc has been told that tx will complete in {in_samples} samples");
+ if let State::TxEnding = self.state {
+ self.state = State::TxEndingAtTime(self.now + in_samples as u64);
}
}
pub fn read_tx_frame(&mut self) -> Option<ModulatorFrame> {
match self.state {
State::Idle | State::RxAcquiringStream(_) | State::RxStream(_) | State::RxPacket(_) => {
- // We will let CSMA decide whether to actually go ahead.
- // That's not implemented yet, so let's just check DCD.
- let channel_free = !self.dcd;
let stream_wants_to_tx = self.stream_pending_lsf.is_some();
let packet_wants_to_tx = self.packet_full || (self.packet_next != self.packet_curr);
- if channel_free && stream_wants_to_tx {
+ if !stream_wants_to_tx && !packet_wants_to_tx {
+ return None;
+ }
+
+ // We have something we might send if the channel is free
+
+ // TODO: Proper full duplex support
+ // A true full duplex TNC should be able to rx and tx concurrently, implying
+ // separate states.
+ if !self.full_duplex {
+ match self.next_csma_check {
+ None => {
+ if self.dcd {
+ self.next_csma_check = Some(self.now + 1920);
+ return None;
+ } else {
+ // channel is idle at the moment we get a frame to send
+ // go right ahead
+ }
+ }
+ Some(at_time) => {
+ if self.now < at_time {
+ return None;
+ }
+ // 25% chance that we'll transmit this slot.
+ // Using self.now as random is probably fine so long as it's not being set in
+ // a lumpy manner. m17app's soundmodem should be fine.
+ // TODO: bring in prng to help in cases where `now` never ends in 0b11
+ let p1_4 = (self.now & 3) == 3;
+ if !self.dcd || !p1_4 {
+ self.next_csma_check = Some(self.now + 1920);
+ return None;
+ } else {
+ self.next_csma_check = None;
+ }
+ }
+ }
+ }
+
+ if stream_wants_to_tx {
self.state = State::TxStream;
- } else if channel_free && packet_wants_to_tx {
- self.state = State::TxPacket;
} else {
- return None;
+ self.state = State::TxPacket;
}
self.ptt = true;
- // TODO: true txdelay
- Some(ModulatorFrame::Preamble { tx_delay: 0 })
+ Some(ModulatorFrame::Preamble {
+ tx_delay: self.tx_delay,
+ })
}
State::TxStream => {
if !self.stream_full && self.stream_next == self.stream_curr {
}
self.stream_curr = (self.stream_curr + 1) % 8;
if frame.end_of_stream {
- self.state = State::Idle;
+ self.state = State::TxStreamSentEndOfStream;
}
Some(ModulatorFrame::Stream(frame))
}
let Ok(port) = kiss_frame.port() else {
continue;
};
+ let Ok(command) = kiss_frame.command() else {
+ continue;
+ };
+ if port != PORT_PACKET_BASIC && port != PORT_PACKET_FULL && port != PORT_STREAM {
+ continue;
+ }
+ if command == KissCommand::TxDelay {
+ let mut new_delay = [0u8; 1];
+ if kiss_frame.decode_payload(&mut new_delay) == Ok(1) {
+ self.tx_delay = new_delay[0];
+ }
+ continue;
+ }
+ if command == KissCommand::FullDuplex {
+ let mut new_duplex = [0u8; 1];
+ if kiss_frame.decode_payload(&mut new_duplex) == Ok(1) {
+ self.full_duplex = new_duplex[0] != 0;
+ }
+ continue;
+ }
+ if command != KissCommand::DataFrame {
+ // Not supporting any other settings yet
+ // TODO: allow adjusting P persistence parameter for CSMA
+ continue;
+ }
if port == PORT_PACKET_BASIC {
+ if self.packet_full {
+ continue;
+ }
+ let mut pending = PendingPacket::new();
+ pending.app_data[0] = 0x00; // RAW
+ let Ok(mut len) = kiss_frame.decode_payload(&mut pending.app_data[1..]) else {
+ continue;
+ };
+ len += 1; // for RAW prefix
+ let packet_crc = crate::crc::m17_crc(&pending.app_data[0..len]);
+ pending.app_data[len..len + 2].copy_from_slice(&packet_crc.to_be_bytes());
+ pending.app_data_len = len + 2;
+ pending.lsf = Some(LsfFrame::new_packet(
+ &Address::Callsign(Callsign(*b"M17RT-PKT")),
+ &Address::Broadcast,
+ ));
+ self.packet_queue[self.packet_next] = pending;
+ self.packet_next = (self.packet_next + 1) % 4;
+ if self.packet_next == self.packet_curr {
+ self.packet_full = true;
+ }
} else if port == PORT_PACKET_FULL {
+ if self.packet_full {
+ continue;
+ }
+ let mut pending = PendingPacket::new();
+ let mut payload = [0u8; 855];
+ let Ok(len) = kiss_frame.decode_payload(&mut payload) else {
+ continue;
+ };
+ if len < 33 {
+ continue;
+ }
+ let mut lsf = LsfFrame([0u8; 30]);
+ lsf.0.copy_from_slice(&payload[0..30]);
+ if lsf.check_crc() != 0 {
+ continue;
+ }
+ pending.lsf = Some(lsf);
+ let app_data_len = len - 30;
+ pending.app_data[0..app_data_len].copy_from_slice(&payload[30..len]);
+ pending.app_data_len = app_data_len;
+ self.packet_queue[self.packet_next] = pending;
+ self.packet_next = (self.packet_next + 1) % 4;
+ if self.packet_next == self.packet_curr {
+ self.packet_full = true;
+ }
} else if port == PORT_STREAM {
+ let mut payload = [0u8; 30];
+ let Ok(len) = kiss_frame.decode_payload(&mut payload) else {
+ continue;
+ };
+ if len < 26 {
+ log::debug!("payload len too short");
+ continue;
+ }
+ if len == 30 {
+ let lsf = LsfFrame(payload);
+ if lsf.check_crc() != 0 {
+ continue;
+ }
+ self.stream_pending_lsf = Some(lsf);
+ } else {
+ if self.stream_full {
+ log::debug!("stream full");
+ continue;
+ }
+ let frame_num_part = u16::from_be_bytes([payload[6], payload[7]]);
+ self.stream_queue[self.stream_next] = StreamFrame {
+ lich_idx: payload[5] >> 5,
+ lich_part: payload[0..5].try_into().unwrap(),
+ frame_number: frame_num_part & 0x7fff,
+ end_of_stream: frame_num_part & 0x8000 > 0,
+ stream_data: payload[8..24].try_into().unwrap(),
+ };
+ self.stream_next = (self.stream_next + 1) % 8;
+ if self.stream_next == self.stream_curr {
+ self.stream_full = true;
+ }
+ }
}
}
n
}
}
+impl Default for SoftTnc {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum SoftTncError {
General(&'static str),
sent: usize,
}
+#[allow(clippy::large_enum_variant)]
enum State {
/// Nothing happening. We may have TX data queued but we won't act on it until CSMA opens up.
Idle,
struct RxStreamState {
/// Track identifying information for this transmission so we can tell if it changes.
- lsf: LsfFrame,
+ _lsf: LsfFrame,
/// Expected next frame number. Allowed to skip values on RX, but not go backwards.
index: u16,
}
impl PendingPacket {
+ fn new() -> Self {
+ Self {
+ lsf: None,
+ app_data: [0u8; 825],
+ app_data_len: 0,
+ app_data_transmitted: 0,
+ }
+ }
+
/// Returns next frame, not including preamble or EOT.
///
/// False means all data frames have been sent.
)
};
let mut payload = [0u8; 25];
- payload.copy_from_slice(
+ payload[0..data_len].copy_from_slice(
&self.app_data[self.app_data_transmitted..(self.app_data_transmitted + data_len)],
);
self.app_data_transmitted += data_len;