From 488cd0f950a1754f8c5a34dc2617c927e466cc3b Mon Sep 17 00:00:00 2001
From: Thomas Karpiniec <tom.karpiniec@outlook.com>
Date: Tue, 21 Jan 2025 21:47:49 +1100
Subject: [PATCH 1/1] Handle KISS configuration commands, crate metadata

---
 m17app/Cargo.toml    |  3 ++
 m17app/README.md     |  0
 m17codec2/Cargo.toml |  3 ++
 m17codec2/README.md  |  0
 m17core/Cargo.toml   |  3 ++
 m17core/README.md    |  0
 m17core/src/tnc.rs   | 94 ++++++++++++++++++++++++++++++++------------
 7 files changed, 77 insertions(+), 26 deletions(-)
 create mode 100644 m17app/README.md
 create mode 100644 m17codec2/README.md
 create mode 100644 m17core/README.md

diff --git a/m17app/Cargo.toml b/m17app/Cargo.toml
index 4a083bd..bbd07f4 100755
--- a/m17app/Cargo.toml
+++ b/m17app/Cargo.toml
@@ -6,6 +6,9 @@ keywords = ["amateur", "radio", "m17", "ham"]
 license = "MIT"
 authors = ["Thomas Karpiniec <tom.karpiniec@outlook.com"]
 repository = "https://code.octet-stream.net/m17rt"
+description = "M17 digital radio - high level API, integration with sound cards, serial PTT and TCP/IP"
+homepage = "https://octet-stream.net/p/m17rt/"
+readme = "README.md"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
diff --git a/m17app/README.md b/m17app/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/m17codec2/Cargo.toml b/m17codec2/Cargo.toml
index 8f074f1..e090160 100755
--- a/m17codec2/Cargo.toml
+++ b/m17codec2/Cargo.toml
@@ -5,7 +5,10 @@ edition = "2021"
 keywords = ["amateur", "radio", "m17", "ham"]
 license = "MIT"
 authors = ["Thomas Karpiniec <tom.karpiniec@outlook.com"]
+description = "M17 digital radio - codec2 stream adapters"
+homepage = "https://octet-stream.net/p/m17rt/"
 repository = "https://code.octet-stream.net/m17rt"
+readme = "README.md"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
diff --git a/m17codec2/README.md b/m17codec2/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/m17core/Cargo.toml b/m17core/Cargo.toml
index 53159c6..42fab94 100755
--- a/m17core/Cargo.toml
+++ b/m17core/Cargo.toml
@@ -6,6 +6,9 @@ keywords = ["amateur", "radio", "m17", "ham", "no_std"]
 license = "MIT"
 authors = ["Thomas Karpiniec <tom.karpiniec@outlook.com"]
 repository = "https://code.octet-stream.net/m17rt"
+description = "M17 digital radio - core protocol definitions, modem, TNC, and KISS"
+homepage = "https://octet-stream.net/p/m17rt/"
+readme = "README.md"
 
 [dependencies]
 cai_golay = "0.1.1"
diff --git a/m17core/README.md b/m17core/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/m17core/src/tnc.rs b/m17core/src/tnc.rs
index a7cbc53..8cd0152 100644
--- a/m17core/src/tnc.rs
+++ b/m17core/src/tnc.rs
@@ -1,5 +1,7 @@
 use crate::address::{Address, Callsign};
-use crate::kiss::{KissBuffer, KissFrame, PORT_PACKET_BASIC, PORT_PACKET_FULL, PORT_STREAM};
+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,
@@ -66,6 +68,12 @@ pub struct SoftTnc {
 
     /// 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 {
@@ -87,6 +95,8 @@ impl SoftTnc {
             stream_curr: 0,
             stream_full: false,
             ptt: false,
+            tx_delay: 0,
+            full_duplex: false,
         }
     }
 
@@ -235,30 +245,36 @@ impl SoftTnc {
                 }
 
                 // We have something we might send if the channel is free
-                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;
+
+                // 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
+                            }
                         }
-                        // 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;
+                        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;
+                            }
                         }
                     }
                 }
@@ -269,8 +285,9 @@ impl SoftTnc {
                     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 {
@@ -348,6 +365,31 @@ impl SoftTnc {
             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;
-- 
2.39.5