lib.rs (6565B)
1 #![allow(clippy::missing_safety_doc)] 2 3 mod civ; 4 mod sync; 5 6 use std::ffi::{c_char, CStr}; 7 use std::sync::atomic::{AtomicBool, Ordering}; 8 use std::sync::{Arc, Mutex, Once}; 9 use std::thread::JoinHandle; 10 11 use log::{debug, trace, Level, Log, Metadata, Record}; 12 13 const DEFAULT_PORT: &str = "/dev/ttyUSB1"; 14 15 extern "C" { 16 fn civ_bridge_tune(freq: f64); 17 fn civ_bridge_get_vfo_freq() -> f64; 18 fn civ_bridge_log(level: i32, msg: *const c_char); 19 fn civ_bridge_get_mode() -> i32; 20 fn civ_bridge_set_mode(mode: i32); 21 fn civ_bridge_get_bandwidth() -> f64; 22 fn civ_bridge_set_bandwidth(hz: f64); 23 } 24 25 struct FlogLogger; 26 27 impl Log for FlogLogger { 28 fn enabled(&self, _metadata: &Metadata) -> bool { 29 true 30 } 31 32 fn log(&self, record: &Record) { 33 let level = match record.level() { 34 Level::Error => 3, 35 Level::Warn => 2, 36 Level::Info => 1, 37 Level::Debug | Level::Trace => 0, 38 }; 39 let msg = std::ffi::CString::new(format!("[civ_sync] {}", record.args())) 40 .unwrap_or_else(|_| std::ffi::CString::new("[civ_sync] <invalid utf8>").unwrap()); 41 unsafe { civ_bridge_log(level, msg.as_ptr()) }; 42 } 43 44 fn flush(&self) {} 45 } 46 47 static LOGGER: FlogLogger = FlogLogger; 48 static INIT: Once = Once::new(); 49 50 fn init_logger() { 51 INIT.call_once(|| { 52 log::set_logger(&LOGGER).ok(); 53 log::set_max_level(log::LevelFilter::Info); 54 }); 55 } 56 57 pub struct CivCtx { 58 name: String, 59 port: String, 60 baud: u32, 61 sync_mode: Arc<AtomicBool>, 62 enabled: bool, 63 status: Arc<Mutex<String>>, 64 stop: Arc<AtomicBool>, 65 handle: Option<JoinHandle<()>>, 66 } 67 68 #[no_mangle] 69 pub unsafe extern "C" fn civ_create(name: *const c_char) -> *mut CivCtx { 70 init_logger(); 71 let name = if name.is_null() { 72 "<unnamed>".to_string() 73 } else { 74 unsafe { CStr::from_ptr(name) } 75 .to_string_lossy() 76 .into_owned() 77 }; 78 let ctx = CivCtx { 79 name: name.clone(), 80 port: DEFAULT_PORT.to_string(), 81 baud: 0, 82 sync_mode: Arc::new(AtomicBool::new(true)), 83 enabled: false, 84 status: Arc::new(Mutex::new("Disconnected".to_string())), 85 stop: Arc::new(AtomicBool::new(false)), 86 handle: None, 87 }; 88 debug!("Rust core created (instance: {})", name); 89 Box::into_raw(Box::new(ctx)) 90 } 91 92 #[no_mangle] 93 pub unsafe extern "C" fn civ_destroy(ctx: *mut CivCtx) { 94 if ctx.is_null() { 95 return; 96 } 97 let mut ctx = Box::from_raw(ctx); 98 if ctx.enabled { 99 stop_thread(&mut ctx); 100 } 101 debug!("Rust core destroyed (instance: {})", ctx.name); 102 } 103 104 #[no_mangle] 105 pub unsafe extern "C" fn civ_post_init(ctx: *mut CivCtx) { 106 if ctx.is_null() { 107 return; 108 } 109 trace!("Rust core post-init (instance: {})", (*ctx).name); 110 } 111 112 #[no_mangle] 113 pub unsafe extern "C" fn civ_enable(ctx: *mut CivCtx) { 114 if ctx.is_null() { 115 return; 116 } 117 let ctx = &mut *ctx; 118 if ctx.enabled { 119 return; 120 } 121 ctx.enabled = true; 122 ctx.stop.store(false, Ordering::Relaxed); 123 let port = ctx.port.clone(); 124 let baud = ctx.baud; 125 let sync_mode = Arc::clone(&ctx.sync_mode); 126 let stop = Arc::clone(&ctx.stop); 127 let status = Arc::clone(&ctx.status); 128 debug!("starting sync thread (instance: {})", ctx.name); 129 ctx.handle = Some(std::thread::spawn(move || { 130 sync::run(port, baud, sync_mode, stop, status); 131 })); 132 } 133 134 #[no_mangle] 135 pub unsafe extern "C" fn civ_disable(ctx: *mut CivCtx) { 136 if ctx.is_null() { 137 return; 138 } 139 let ctx = &mut *ctx; 140 if ctx.enabled { 141 stop_thread(ctx); 142 } 143 } 144 145 fn stop_thread(ctx: &mut CivCtx) { 146 ctx.enabled = false; 147 ctx.stop.store(true, Ordering::Relaxed); 148 if let Some(handle) = ctx.handle.take() { 149 let _ = handle.join(); 150 } 151 if let Ok(mut s) = ctx.status.lock() { 152 *s = "Disconnected".to_string(); 153 } 154 } 155 156 #[no_mangle] 157 pub unsafe extern "C" fn civ_is_enabled(ctx: *mut CivCtx) -> bool { 158 if ctx.is_null() { 159 return false; 160 } 161 (*ctx).enabled 162 } 163 164 #[no_mangle] 165 pub unsafe extern "C" fn civ_status(ctx: *mut CivCtx, out: *mut c_char, out_len: usize) { 166 if ctx.is_null() || out.is_null() || out_len == 0 { 167 return; 168 } 169 let text = { 170 let s = (*ctx).status.lock().unwrap(); 171 s.clone() 172 }; 173 let bytes = text.as_bytes(); 174 let n = bytes.len().min(out_len - 1); 175 std::ptr::copy_nonoverlapping(bytes.as_ptr(), out as *mut u8, n); 176 *out.add(n) = 0; 177 } 178 179 #[no_mangle] 180 pub unsafe extern "C" fn civ_set_serial_port(ctx: *mut CivCtx, port: *const c_char) { 181 if ctx.is_null() { 182 return; 183 } 184 let port = if port.is_null() { 185 DEFAULT_PORT.to_string() 186 } else { 187 unsafe { CStr::from_ptr(port) } 188 .to_string_lossy() 189 .into_owned() 190 }; 191 debug!("serial port set to {}", port); 192 (*ctx).port = port; 193 } 194 195 #[no_mangle] 196 pub unsafe extern "C" fn civ_set_serial_baud(ctx: *mut CivCtx, baud: u32) { 197 if ctx.is_null() { 198 return; 199 } 200 debug!( 201 "serial baud set to {}", 202 if baud == 0 { 203 "auto".to_string() 204 } else { 205 baud.to_string() 206 } 207 ); 208 (*ctx).baud = baud; 209 } 210 211 #[no_mangle] 212 pub unsafe extern "C" fn civ_set_sync_mode(ctx: *mut CivCtx, sync_mode: bool) { 213 if ctx.is_null() { 214 return; 215 } 216 debug!("mode/filter sync set to {}", sync_mode); 217 (*ctx).sync_mode.store(sync_mode, Ordering::Relaxed); 218 } 219 220 /// Fill `out` (up to `out_len` bytes) with NUL-separated serial port names 221 /// (double-NUL terminated) from `serialport::available_ports()`, returning the 222 /// number of names written. Used by the shim to build the device dropdown. 223 #[no_mangle] 224 pub unsafe extern "C" fn civ_list_serial_ports(out: *mut c_char, out_len: usize) -> usize { 225 if out.is_null() || out_len < 2 { 226 return 0; 227 } 228 let ports = serialport::available_ports().unwrap_or_default(); 229 let buf = std::slice::from_raw_parts_mut(out as *mut u8, out_len); 230 let mut pos = 0usize; 231 let mut count = 0usize; 232 for p in &ports { 233 if p.port_name.is_empty() { 234 continue; 235 } 236 let name = p.port_name.as_bytes(); 237 // Need room for the name, its NUL, and the final double-NUL byte. 238 if pos + name.len() + 2 > out_len { 239 break; 240 } 241 buf[pos..pos + name.len()].copy_from_slice(name); 242 pos += name.len(); 243 buf[pos] = 0; 244 pos += 1; 245 count += 1; 246 } 247 buf[pos] = 0; // double-NUL terminator 248 count 249 }