2021-02-28 19:41:21 -08:00
|
|
|
use std::ffi::{CStr, CString};
|
2021-02-28 20:14:21 -08:00
|
|
|
use libc::{c_char, c_int, c_uint};
|
2021-02-28 01:19:31 -08:00
|
|
|
use std::os::unix::io::AsRawFd;
|
2021-03-01 00:11:41 -08:00
|
|
|
use std::process::exit;
|
2021-02-28 01:19:31 -08:00
|
|
|
|
2021-03-01 01:52:37 -08:00
|
|
|
use libc::FILE;
|
2021-02-28 12:23:30 -08:00
|
|
|
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
2021-02-28 02:32:10 -08:00
|
|
|
|
2021-03-01 00:54:36 -08:00
|
|
|
pub struct Radio {
|
|
|
|
ptr: *const radio_device_t
|
|
|
|
}
|
|
|
|
|
2021-03-01 02:54:19 -08:00
|
|
|
static mut RADIO_TABLE: [(&'static str, &'static radio_device_t); 16] = unsafe {
|
|
|
|
[
|
2021-03-01 22:20:28 -08:00
|
|
|
("DR780", &radio_md380), // TYT MD-380, Retevis RT3, RT8
|
2021-03-01 02:54:19 -08:00
|
|
|
("MD390", &radio_md390), // TYT MD-390
|
|
|
|
("MD-UV380", &radio_uv380), // TYT MD-UV380
|
|
|
|
("MD-UV390", &radio_uv390), // TYT MD-UV390, Retevis RT3S
|
|
|
|
("2017", &radio_md2017), // TYT MD-2017, Retevis RT82
|
|
|
|
("MD9600", &radio_md9600), // TYT MD-9600
|
|
|
|
("BF-5R", &radio_rd5r), // Baofeng RD-5R, TD-5R
|
|
|
|
("1801", &radio_dm1801), // Baofeng DM-1801
|
|
|
|
("DM-1701", &radio_rt84), // Baofeng DM-1701, Retevis RT84
|
|
|
|
("MD-760P", &radio_gd77), // Radioddity GD-77, version 3.1.1 and later
|
|
|
|
("D868UVE", &radio_d868uv), // Anytone AT-D868UV
|
|
|
|
("D878UV", &radio_d878uv), // Anytone AT-D878UV
|
|
|
|
("D6X2UV", &radio_dmr6x2), // BTECH DMR-6x2
|
|
|
|
("ZD3688", &radio_d900), // Zastone D900
|
|
|
|
("TP660", &radio_dp880), // Zastone DP880
|
|
|
|
("ZN><:", &radio_rt27d), // Radtel RT-27D
|
|
|
|
]
|
|
|
|
};
|
|
|
|
|
2021-03-01 23:25:54 -08:00
|
|
|
// Radio memory contents, up to 2 Mbytes
|
|
|
|
#[no_mangle]
|
|
|
|
pub static mut radio_mem: [u8; 1024*1024*2] = [0; 1024*1024*2];
|
|
|
|
|
2021-02-27 23:16:01 -08:00
|
|
|
extern {
|
2021-02-28 19:41:21 -08:00
|
|
|
|
2021-02-28 20:14:21 -08:00
|
|
|
fn dfu_init(vid: c_uint, pid: c_uint) -> *const c_char;
|
2021-02-28 11:33:05 -08:00
|
|
|
fn dfu_reboot();
|
|
|
|
fn dfu_close();
|
2021-02-28 20:14:21 -08:00
|
|
|
|
|
|
|
fn hid_init(vid: c_int, pid: c_int) -> c_int;
|
|
|
|
fn hid_identify() -> *const c_char;
|
2021-02-28 11:33:05 -08:00
|
|
|
fn hid_close();
|
2021-02-28 20:14:21 -08:00
|
|
|
|
2021-03-04 00:44:52 -08:00
|
|
|
fn serial_identify(s: *const c_char) -> *const c_char;
|
2021-02-28 11:33:05 -08:00
|
|
|
fn serial_close();
|
2021-02-27 23:16:01 -08:00
|
|
|
}
|
|
|
|
|
2021-03-01 23:32:38 -08:00
|
|
|
|
|
|
|
static mut active_device: *const radio_device_t = std::ptr::null(); // Device-dependent interface
|
|
|
|
|
|
|
|
unsafe fn get_active_device() -> *const radio_device_t {
|
|
|
|
return active_device;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn set_active_device(d: *const radio_device_t) {
|
|
|
|
active_device = d;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-28 02:53:19 -08:00
|
|
|
/// Connect to the radio via the serial port.
|
2021-02-28 20:14:21 -08:00
|
|
|
/// and identify the type of device.
|
2021-03-01 00:54:36 -08:00
|
|
|
pub fn connect() -> Radio {
|
2021-02-28 01:31:29 -08:00
|
|
|
unsafe {
|
2021-03-01 00:11:41 -08:00
|
|
|
let mut ident: *const c_char;
|
2021-02-28 20:14:21 -08:00
|
|
|
// Try TYT MD family.
|
|
|
|
ident = dfu_init(0x0483, 0xdf11);
|
|
|
|
if ident.is_null() {
|
|
|
|
// Try RD-5R, DM-1801 and GD-77.
|
2021-03-01 00:11:41 -08:00
|
|
|
if hid_init(0x15a2, 0x0073) >= 0 {
|
2021-02-28 20:14:21 -08:00
|
|
|
ident = hid_identify();
|
2021-03-01 00:11:41 -08:00
|
|
|
}
|
2021-02-28 20:14:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if ident.is_null() {
|
|
|
|
// Try AT-D868UV.
|
2021-03-04 00:44:52 -08:00
|
|
|
let trace_flag = false; //TODO fix
|
|
|
|
if let Some(device_path) = crate::serial::serial_init(0x28e9, 0x018a, trace_flag) {
|
|
|
|
let ptr = device_path.as_ptr() as *mut c_char;
|
|
|
|
ident = serial_identify(ptr);
|
2021-03-01 00:11:41 -08:00
|
|
|
}
|
2021-02-28 20:14:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if ident.is_null() {
|
|
|
|
eprintln!("No radio detected.");
|
|
|
|
eprintln!("Check your USB cable!");
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
let ident_str = CStr::from_ptr(ident).to_str().unwrap();
|
2021-03-01 00:11:41 -08:00
|
|
|
|
|
|
|
let mut device: *const radio_device_t = std::ptr::null();
|
2021-02-28 20:14:21 -08:00
|
|
|
|
2021-03-01 03:03:54 -08:00
|
|
|
for (table_ident, device_ptr) in RADIO_TABLE.iter() {
|
|
|
|
if ident_str == *table_ident {
|
|
|
|
device = *device_ptr as *const radio_device_t;
|
2021-02-28 20:14:21 -08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-03-01 00:11:41 -08:00
|
|
|
|
2021-02-28 20:14:21 -08:00
|
|
|
if device.is_null() {
|
|
|
|
eprintln!("Unrecognized radio '{}'.", ident_str);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
2021-03-01 00:11:41 -08:00
|
|
|
let name_ptr = (*device).name;
|
|
|
|
let name = CStr::from_ptr(name_ptr).to_str().unwrap();
|
|
|
|
println!("Connect to {}", name);
|
2021-02-28 20:14:21 -08:00
|
|
|
|
2021-03-01 00:11:41 -08:00
|
|
|
set_active_device(device);
|
2021-03-01 01:06:01 -08:00
|
|
|
return Radio { ptr: device as *mut radio_device_t };
|
2021-02-28 01:31:29 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 02:53:19 -08:00
|
|
|
/// Close the serial port.
|
2021-02-28 01:31:29 -08:00
|
|
|
pub fn disconnect() {
|
2021-02-28 11:33:05 -08:00
|
|
|
|
|
|
|
eprintln!("Close device.");
|
2021-02-28 01:31:29 -08:00
|
|
|
unsafe {
|
2021-02-28 11:33:05 -08:00
|
|
|
// Restore the normal radio mode.
|
|
|
|
dfu_reboot();
|
|
|
|
dfu_close();
|
|
|
|
hid_close();
|
|
|
|
serial_close();
|
2021-02-28 01:31:29 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 02:53:19 -08:00
|
|
|
/// Read firmware image from the device
|
2021-03-01 00:54:36 -08:00
|
|
|
pub fn download(radio: &Radio, trace: bool) {
|
|
|
|
|
2021-03-01 01:06:01 -08:00
|
|
|
let device = radio.ptr as *mut radio_device_t;
|
2021-03-01 00:43:25 -08:00
|
|
|
|
|
|
|
if !trace {
|
|
|
|
eprint!("Read device: ");
|
|
|
|
}
|
|
|
|
|
2021-02-28 01:31:29 -08:00
|
|
|
unsafe {
|
2021-03-01 00:43:25 -08:00
|
|
|
let download_fn = (*device).download.unwrap();
|
2021-03-01 01:06:01 -08:00
|
|
|
download_fn(device);
|
2021-03-01 00:43:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if !trace {
|
|
|
|
eprintln!(" done.");
|
2021-02-28 01:31:29 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 02:53:19 -08:00
|
|
|
/// Write firmware image to the device.
|
2021-03-01 01:06:01 -08:00
|
|
|
pub fn upload(radio: &Radio, cont_flag: c_int, trace: bool) {
|
|
|
|
let device = radio.ptr as *mut radio_device_t;
|
2021-02-28 02:38:07 -08:00
|
|
|
unsafe {
|
2021-03-01 01:06:01 -08:00
|
|
|
let is_compatible_fn = (*device).is_compatible.unwrap();
|
|
|
|
if is_compatible_fn(device) == 0 {
|
|
|
|
eprintln!("Incompatible image - cannot upload.");
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if !trace {
|
|
|
|
eprint!("Write device: ");
|
|
|
|
}
|
|
|
|
|
|
|
|
let upload_fn = (*device).upload.unwrap();
|
|
|
|
upload_fn(device, cont_flag);
|
|
|
|
|
|
|
|
if !trace {
|
|
|
|
eprintln!(" done.");
|
|
|
|
}
|
2021-02-28 02:38:07 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 02:53:19 -08:00
|
|
|
/// List all supported radios.
|
2021-02-27 23:37:41 -08:00
|
|
|
pub fn list() {
|
2021-02-28 02:32:10 -08:00
|
|
|
|
2021-02-28 19:41:21 -08:00
|
|
|
println!("Supported radios:");
|
|
|
|
|
2021-02-27 23:16:01 -08:00
|
|
|
unsafe {
|
2021-03-01 02:57:53 -08:00
|
|
|
for (_, device) in RADIO_TABLE.iter() {
|
|
|
|
let name_ptr = (*device).name;
|
2021-02-28 19:41:21 -08:00
|
|
|
let name = CStr::from_ptr(name_ptr).to_str().unwrap().to_string();
|
|
|
|
println!(" {}", name);
|
|
|
|
}
|
2021-02-27 23:16:01 -08:00
|
|
|
}
|
|
|
|
}
|
2021-02-28 01:19:31 -08:00
|
|
|
|
2021-03-01 01:52:37 -08:00
|
|
|
/// Check that the configuration is correct.
|
2021-03-01 00:54:36 -08:00
|
|
|
pub fn verify_config(radio: &Radio) {
|
2021-03-01 01:52:37 -08:00
|
|
|
let device = radio.ptr as *mut radio_device_t;
|
2021-02-28 01:31:29 -08:00
|
|
|
unsafe {
|
2021-03-01 01:52:37 -08:00
|
|
|
let verify_fn = (*device).verify_config.unwrap();
|
|
|
|
if verify_fn(device) == 0 {
|
|
|
|
// Message should be already printed.
|
|
|
|
exit(-1);
|
|
|
|
}
|
2021-02-28 01:31:29 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 02:53:19 -08:00
|
|
|
/// Read firmware image from the binary file.
|
2021-03-01 00:54:36 -08:00
|
|
|
pub fn read_image(filename: &str) -> Radio {
|
2021-03-01 19:43:01 -08:00
|
|
|
use std::io::{Seek, SeekFrom, Read};
|
|
|
|
|
|
|
|
fn read_header(file: &mut std::fs::File, filename: &str) -> Vec<u8> {
|
|
|
|
let mut header_buf: [u8; 7] = [0; 7];
|
|
|
|
match file.read(&mut header_buf) {
|
|
|
|
Ok(7) => (),
|
|
|
|
Ok(_) => {
|
|
|
|
eprintln!("{}: Cannot read header.", filename);
|
|
|
|
exit(-1);
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("{}: Cannot read header. Error: {}", filename, e);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
file.seek(SeekFrom::Start(0)).unwrap();
|
|
|
|
|
|
|
|
header_buf.to_vec()
|
|
|
|
}
|
2021-03-01 02:29:53 -08:00
|
|
|
|
2021-03-01 19:43:01 -08:00
|
|
|
eprintln!("Read codeplug from file '{}'.", filename);
|
2021-03-01 02:29:53 -08:00
|
|
|
|
2021-03-01 19:43:01 -08:00
|
|
|
let mut img_file = std::fs::File::open(filename).unwrap();
|
2021-03-01 02:29:53 -08:00
|
|
|
let metadata = std::fs::metadata(filename).unwrap();
|
|
|
|
let file_size = metadata.len();
|
|
|
|
|
2021-03-01 19:43:01 -08:00
|
|
|
let device = unsafe {match file_size {
|
|
|
|
851968 | 852533 => {
|
|
|
|
&radio_uv380
|
2021-03-01 02:29:53 -08:00
|
|
|
}
|
|
|
|
262144 | 262709 => {
|
2021-03-01 19:43:01 -08:00
|
|
|
&radio_md380
|
2021-03-01 02:29:53 -08:00
|
|
|
}
|
|
|
|
1606528 => {
|
2021-03-01 19:43:01 -08:00
|
|
|
let header = read_header(&mut img_file, filename);
|
|
|
|
if header.as_slice().starts_with(b"D868UVE") {
|
|
|
|
&radio_d868uv
|
|
|
|
} else if header.as_slice().starts_with(b"D878UV") {
|
|
|
|
&radio_d878uv
|
|
|
|
} else if header.as_slice().starts_with(b"D6X2UV") {
|
|
|
|
&radio_dmr6x2
|
|
|
|
} else {
|
|
|
|
match std::str::from_utf8(&header) {
|
|
|
|
Ok(s) => eprintln!("Unrecognized header: {}", s),
|
|
|
|
Err(_) => eprintln!("Unrecognized header: {:?}", header),
|
|
|
|
};
|
|
|
|
|
|
|
|
exit(-1)
|
|
|
|
}
|
2021-03-01 02:29:53 -08:00
|
|
|
}
|
|
|
|
131072 => {
|
2021-03-01 19:43:01 -08:00
|
|
|
let header = read_header(&mut img_file, filename);
|
|
|
|
if header.as_slice().starts_with(b"BF-5R") {
|
|
|
|
&radio_rd5r
|
|
|
|
} else if header.as_slice().starts_with(b"MD-760P") {
|
|
|
|
&radio_gd77
|
|
|
|
} else if header.as_slice().starts_with(b"1801") {
|
|
|
|
&radio_dm1801
|
|
|
|
} else if header.as_slice().starts_with(b"MD-760") {
|
|
|
|
eprintln!("Old Radioddity GD-77 v2.6 image not supported!");
|
|
|
|
exit(-1)
|
|
|
|
} else {
|
|
|
|
match std::str::from_utf8(&header) {
|
|
|
|
Ok(s) => eprintln!("Unrecognized header: {}", s),
|
|
|
|
Err(_) => eprintln!("Unrecognized header: {:?}", header),
|
|
|
|
};
|
|
|
|
|
|
|
|
exit(-1)
|
2021-03-01 02:29:53 -08:00
|
|
|
|
2021-03-01 19:43:01 -08:00
|
|
|
}
|
2021-03-01 02:29:53 -08:00
|
|
|
}
|
|
|
|
size => {
|
|
|
|
eprintln!("{}: Unrecognized file size {} bytes.", filename, size);
|
2021-03-01 19:43:01 -08:00
|
|
|
exit(-1)
|
2021-03-01 02:29:53 -08:00
|
|
|
}
|
2021-03-01 19:43:01 -08:00
|
|
|
} };
|
|
|
|
|
|
|
|
let fd = img_file.as_raw_fd();
|
|
|
|
let mode = CString::new("rb").unwrap();
|
|
|
|
unsafe {
|
|
|
|
let device = device as *const radio_device_t;
|
|
|
|
let device = device as *mut radio_device_t;
|
|
|
|
let file = libc::fdopen(fd, mode.as_ptr());
|
|
|
|
let read_image_fn = (*device).read_image.unwrap();
|
|
|
|
|
|
|
|
read_image_fn(device, file);
|
|
|
|
libc::fclose(file);
|
|
|
|
set_active_device(device);
|
|
|
|
Radio { ptr: device }
|
|
|
|
}
|
2021-03-01 02:29:53 -08:00
|
|
|
|
2021-02-28 01:19:31 -08:00
|
|
|
}
|
|
|
|
|
2021-02-28 02:53:19 -08:00
|
|
|
/// Save firmware image to the binary file.
|
2021-03-01 00:54:36 -08:00
|
|
|
pub fn save_image(radio: &Radio, filename: &str) {
|
2021-03-01 02:02:17 -08:00
|
|
|
|
|
|
|
eprintln!("Write codeplug to file '{}'.", filename);
|
|
|
|
|
|
|
|
let device = radio.ptr as *mut radio_device_t;
|
|
|
|
let file = std::fs::File::create(filename).unwrap();
|
|
|
|
let fd = file.as_raw_fd();
|
|
|
|
let mode = CString::new("wb").unwrap();
|
|
|
|
|
2021-02-28 01:19:31 -08:00
|
|
|
unsafe {
|
2021-03-01 02:02:17 -08:00
|
|
|
let file = libc::fdopen(fd, mode.as_ptr());
|
|
|
|
let save_image_fn = (*device).save_image.unwrap();
|
|
|
|
save_image_fn(device, file);
|
|
|
|
libc::fclose(file);
|
2021-02-28 01:19:31 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-03 00:49:57 -08:00
|
|
|
/// Read the configuration from a text file, and modify the firmware.
|
2021-03-01 00:54:36 -08:00
|
|
|
pub fn parse_config(radio: &Radio, filename: &str) {
|
2021-03-03 00:49:57 -08:00
|
|
|
use std::io::{BufRead, BufReader};
|
|
|
|
|
|
|
|
let device = radio.ptr as *mut radio_device_t;
|
|
|
|
|
|
|
|
let parse_header_fn = unsafe {
|
|
|
|
(*device).parse_header.unwrap()
|
|
|
|
};
|
|
|
|
|
|
|
|
let parse_parameter_fn = unsafe {
|
|
|
|
(*device).parse_parameter.unwrap()
|
|
|
|
};
|
|
|
|
|
|
|
|
let parse_row_fn = unsafe {
|
|
|
|
(*device).parse_row.unwrap()
|
|
|
|
};
|
|
|
|
|
|
|
|
let update_timestamp_fn = unsafe {
|
|
|
|
(*device).update_timestamp.unwrap()
|
|
|
|
};
|
|
|
|
|
|
|
|
eprintln!("Read configuration from file '{}'.", filename);
|
|
|
|
let file = std::fs::File::open(filename).unwrap();
|
|
|
|
let file = BufReader::new(file);
|
|
|
|
|
|
|
|
let mut table_id: c_int = 0;
|
|
|
|
let mut table_dirty: c_int = 0;
|
|
|
|
|
|
|
|
for line in file.lines() {
|
|
|
|
let line = line.unwrap();
|
|
|
|
// Strip text after comment marker '#'
|
|
|
|
let trimmed_line = line
|
|
|
|
.split('#')
|
|
|
|
.next()
|
|
|
|
.unwrap()
|
|
|
|
.trim_end();
|
|
|
|
|
|
|
|
// Skip comments and blank lines
|
|
|
|
if trimmed_line.is_empty() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Table row
|
|
|
|
if trimmed_line.chars().nth(0) == Some(' ') {
|
|
|
|
if table_id == 0 {
|
|
|
|
eprintln!("Invalid line: '{}'", line);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
let trimmed_more = CString::new(trimmed_line.trim_start()).unwrap();
|
|
|
|
let ptr = trimmed_more.as_ptr() as *mut c_char;
|
|
|
|
let output = unsafe { parse_row_fn(device, table_id, !table_dirty, ptr) };
|
|
|
|
if output == 0 {
|
|
|
|
eprintln!("Invalid line: '{}'", line);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
table_dirty = 1;
|
|
|
|
} else {
|
|
|
|
// Table finished
|
|
|
|
table_id = 0;
|
|
|
|
|
|
|
|
// Find the value
|
|
|
|
if let Some(colon_byte_idx) = line.find(':') {
|
|
|
|
|
|
|
|
let (param_str, value_str) = line.split_at(colon_byte_idx);
|
|
|
|
let stripped_value_str = value_str.strip_prefix(':').unwrap();
|
|
|
|
|
|
|
|
let param_c_str = CString::new(param_str.trim_start()).unwrap();
|
|
|
|
let param_ptr = param_c_str.as_ptr() as *mut c_char;
|
|
|
|
|
|
|
|
let value_c_str = CString::new(stripped_value_str.trim_start()).unwrap();
|
|
|
|
let value_ptr = value_c_str.as_ptr() as *mut c_char;
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
parse_parameter_fn(device, param_ptr, value_ptr);
|
|
|
|
}
|
|
|
|
} else { // Table header
|
|
|
|
let c_line = CString::new(trimmed_line).unwrap();
|
|
|
|
let ptr = c_line.as_ptr() as *mut c_char;
|
|
|
|
table_id = unsafe { parse_header_fn(device, ptr) };
|
|
|
|
|
|
|
|
if table_id == 0 {
|
|
|
|
eprintln!("Invalid line: '{}'", line);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
table_dirty = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 01:19:31 -08:00
|
|
|
unsafe {
|
2021-03-03 00:49:57 -08:00
|
|
|
update_timestamp_fn(device);
|
2021-02-28 01:19:31 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 02:53:19 -08:00
|
|
|
/// Print full information about the device configuration.
|
2021-03-01 20:46:53 -08:00
|
|
|
/// If `filename` is `None`, write to stdout.
|
|
|
|
pub fn print_config(radio: &Radio, filename: Option<&str>) {
|
|
|
|
|
2021-03-01 21:14:53 -08:00
|
|
|
use std::io::Write;
|
2021-03-01 20:46:53 -08:00
|
|
|
|
2021-03-01 21:14:53 -08:00
|
|
|
fn make_config_string() -> String {
|
|
|
|
use chrono::Datelike;
|
|
|
|
let date = chrono::offset::Local::today();
|
|
|
|
let y = date.year(); let m = date.month(); let d = date.day();
|
|
|
|
format!("#\n# Configuration generated {}/{}/{} by dmrconfig, version {}\n#\n", y, m, d, crate::version())
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut file;
|
|
|
|
|
|
|
|
let (fd, verbose) = match filename {
|
2021-03-01 20:46:53 -08:00
|
|
|
None => {
|
|
|
|
unsafe {
|
2021-03-01 21:14:53 -08:00
|
|
|
let verbose = libc::isatty(libc::STDOUT_FILENO) != 1;
|
|
|
|
if verbose {
|
|
|
|
print!("{}", make_config_string());
|
|
|
|
}
|
|
|
|
(libc::STDOUT_FILENO, verbose)
|
2021-03-01 20:46:53 -08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
Some(filename) => {
|
|
|
|
file = std::fs::File::create(filename).unwrap();
|
2021-03-01 21:14:53 -08:00
|
|
|
file.write_all(make_config_string().as_bytes()).unwrap();
|
|
|
|
(file.as_raw_fd(), true)
|
2021-03-01 20:46:53 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-02-28 01:19:31 -08:00
|
|
|
let mode = CString::new("w").unwrap();
|
2021-03-01 20:46:53 -08:00
|
|
|
|
2021-02-28 01:19:31 -08:00
|
|
|
unsafe {
|
2021-03-01 21:14:53 -08:00
|
|
|
let device = radio.ptr as *mut radio_device_t;
|
|
|
|
let print_config_fn = (*device).print_config.unwrap();
|
2021-03-01 00:54:36 -08:00
|
|
|
let file = libc::fdopen(fd, mode.as_ptr());
|
2021-03-01 21:14:53 -08:00
|
|
|
print_config_fn(device, file, if verbose { 1 } else { 0 } );
|
2021-02-28 01:19:31 -08:00
|
|
|
libc::fclose(file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 02:53:19 -08:00
|
|
|
/// Print generic information about the device.
|
2021-03-01 00:54:36 -08:00
|
|
|
pub fn print_version(radio: &Radio) {
|
2021-03-01 01:52:37 -08:00
|
|
|
let device = radio.ptr as *mut radio_device_t;
|
2021-02-28 01:19:31 -08:00
|
|
|
let mode = CString::new("w").unwrap();
|
|
|
|
unsafe {
|
2021-03-01 01:52:37 -08:00
|
|
|
let print_version_fn = (*device).print_version.unwrap();
|
|
|
|
print_version_fn(device, libc::fdopen(libc::STDOUT_FILENO, mode.as_ptr()));
|
2021-02-28 01:19:31 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-01 22:20:28 -08:00
|
|
|
/// Update contacts database on the device.
|
2021-03-01 00:54:36 -08:00
|
|
|
pub fn write_csv(radio: &Radio, filename: &str) {
|
2021-03-01 22:20:28 -08:00
|
|
|
let device = radio.ptr as *mut radio_device_t;
|
2021-02-28 01:19:31 -08:00
|
|
|
unsafe {
|
2021-03-01 22:20:28 -08:00
|
|
|
let write_csv_fn = match (*device).write_csv {
|
|
|
|
Some(func) => func,
|
|
|
|
None => {
|
|
|
|
let name_ptr = (*device).name;
|
|
|
|
let name = CStr::from_ptr(name_ptr).to_str().unwrap();
|
|
|
|
eprintln!("{} does not support CSV database.", name);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let file = std::fs::File::open(filename).unwrap();
|
|
|
|
eprintln!("Read file '{}.", filename);
|
|
|
|
|
|
|
|
let fd = file.as_raw_fd();
|
|
|
|
let mode = CString::new("r").unwrap();
|
|
|
|
let file = libc::fdopen(fd, mode.as_ptr());
|
|
|
|
write_csv_fn(device, file);
|
|
|
|
libc::fclose(file);
|
2021-02-28 01:19:31 -08:00
|
|
|
}
|
|
|
|
}
|
2021-03-01 03:18:39 -08:00
|
|
|
|
|
|
|
/// Check for compatible radio model.
|
|
|
|
#[no_mangle]
|
|
|
|
pub extern "C" fn radio_is_compatible(name: *const c_char) -> c_int {
|
|
|
|
unsafe {
|
|
|
|
let name = CStr::from_ptr(name).to_str().unwrap();
|
|
|
|
let dev = get_active_device();
|
|
|
|
|
|
|
|
for (_, device) in RADIO_TABLE.iter() {
|
|
|
|
// Radio is compatible when it has the same parse routine.
|
|
|
|
let same_parse = (*dev).parse_parameter.unwrap() == (*device).parse_parameter.unwrap();
|
|
|
|
let name_ptr = (*device).name;
|
|
|
|
let device_name = CStr::from_ptr(name_ptr).to_str().unwrap();
|
|
|
|
let same_name = name.eq_ignore_ascii_case(device_name);
|
|
|
|
|
|
|
|
if same_parse && same_name {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|