dmrconfig/src/radio.rs

379 lines
10 KiB
Rust

use std::ffi::{CStr, CString};
use libc::{c_char, c_int, c_uint};
use std::os::unix::io::AsRawFd;
use std::process::exit;
use libc::FILE;
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
pub struct Radio {
ptr: *const radio_device_t
}
static mut RADIO_TABLE: [(&'static str, &'static radio_device_t); 16] = unsafe {
[
("DR780", &radio_md380), // TYT MD-380, Retevis RT3, RT8
("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
]
};
extern {
fn get_active_device() -> *const radio_device_t;
fn set_active_device(device: *const radio_device_t);
fn radio_print_config(device: *const radio_device_t, file: *const libc::FILE, verbose: c_int);
fn radio_parse_config(device: *const radio_device_t, filename: *const c_char);
fn radio_write_csv(device: *const radio_device_t, filename: *const c_char);
fn dfu_init(vid: c_uint, pid: c_uint) -> *const c_char;
fn dfu_reboot();
fn dfu_close();
fn hid_init(vid: c_int, pid: c_int) -> c_int;
fn hid_identify() -> *const c_char;
fn hid_close();
fn serial_init(vid: c_int, pid: c_int) -> c_int;
fn serial_identify() -> *const c_char;
fn serial_close();
}
/// Connect to the radio via the serial port.
/// and identify the type of device.
pub fn connect() -> Radio {
unsafe {
let mut ident: *const c_char;
// Try TYT MD family.
ident = dfu_init(0x0483, 0xdf11);
if ident.is_null() {
// Try RD-5R, DM-1801 and GD-77.
if hid_init(0x15a2, 0x0073) >= 0 {
ident = hid_identify();
}
}
if ident.is_null() {
// Try AT-D868UV.
if serial_init(0x28e9, 0x018a) >= 0 {
ident = serial_identify();
}
}
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();
let mut device: *const radio_device_t = std::ptr::null();
for (table_ident, device_ptr) in RADIO_TABLE.iter() {
if ident_str == *table_ident {
device = *device_ptr as *const radio_device_t;
break;
}
}
if device.is_null() {
eprintln!("Unrecognized radio '{}'.", ident_str);
exit(-1);
}
let name_ptr = (*device).name;
let name = CStr::from_ptr(name_ptr).to_str().unwrap();
println!("Connect to {}", name);
set_active_device(device);
return Radio { ptr: device as *mut radio_device_t };
}
}
/// Close the serial port.
pub fn disconnect() {
eprintln!("Close device.");
unsafe {
// Restore the normal radio mode.
dfu_reboot();
dfu_close();
hid_close();
serial_close();
}
}
/// Read firmware image from the device
pub fn download(radio: &Radio, trace: bool) {
let device = radio.ptr as *mut radio_device_t;
if !trace {
eprint!("Read device: ");
}
unsafe {
let download_fn = (*device).download.unwrap();
download_fn(device);
}
if !trace {
eprintln!(" done.");
}
}
/// Write firmware image to the device.
pub fn upload(radio: &Radio, cont_flag: c_int, trace: bool) {
let device = radio.ptr as *mut radio_device_t;
unsafe {
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.");
}
}
}
/// List all supported radios.
pub fn list() {
println!("Supported radios:");
unsafe {
for (_, device) in RADIO_TABLE.iter() {
let name_ptr = (*device).name;
let name = CStr::from_ptr(name_ptr).to_str().unwrap().to_string();
println!(" {}", name);
}
}
}
/// Check that the configuration is correct.
pub fn verify_config(radio: &Radio) {
let device = radio.ptr as *mut radio_device_t;
unsafe {
let verify_fn = (*device).verify_config.unwrap();
if verify_fn(device) == 0 {
// Message should be already printed.
exit(-1);
}
}
}
/// Read firmware image from the binary file.
pub fn read_image(filename: &str) -> Radio {
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()
}
eprintln!("Read codeplug from file '{}'.", filename);
let mut img_file = std::fs::File::open(filename).unwrap();
let metadata = std::fs::metadata(filename).unwrap();
let file_size = metadata.len();
let device = unsafe {match file_size {
851968 | 852533 => {
&radio_uv380
}
262144 | 262709 => {
&radio_md380
}
1606528 => {
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)
}
}
131072 => {
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)
}
}
size => {
eprintln!("{}: Unrecognized file size {} bytes.", filename, size);
exit(-1)
}
} };
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 }
}
}
/// Save firmware image to the binary file.
pub fn save_image(radio: &Radio, filename: &str) {
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();
unsafe {
let file = libc::fdopen(fd, mode.as_ptr());
let save_image_fn = (*device).save_image.unwrap();
save_image_fn(device, file);
libc::fclose(file);
}
}
/// Read the configuration from text file, and modify the firmware.
pub fn parse_config(radio: &Radio, filename: &str) {
let device = radio.ptr;
let filename = CString::new(filename.to_string()).unwrap();
unsafe {
radio_parse_config(device, filename.as_ptr())
}
}
/// Print full information about the device configuration.
pub fn print_config(radio: &Radio, filename: &str) {
let device = radio.ptr;
let file = std::fs::File::create(filename).unwrap();
let fd = file.as_raw_fd();
let mode = CString::new("w").unwrap();
unsafe {
let file = libc::fdopen(fd, mode.as_ptr());
radio_print_config(device, file, 1);
libc::fclose(file);
}
}
pub fn print_config_to_stdout(radio: &Radio) {
let device = radio.ptr;
let mode = CString::new("w").unwrap();
unsafe {
let stdout = libc::fdopen(libc::STDOUT_FILENO, mode.as_ptr());
let verbosity = if libc::isatty(libc::STDOUT_FILENO) == 1 {
0
} else {
1
};
radio_print_config(device, stdout, verbosity);
}
}
/// Print generic information about the device.
pub fn print_version(radio: &Radio) {
let device = radio.ptr as *mut radio_device_t;
let mode = CString::new("w").unwrap();
unsafe {
let print_version_fn = (*device).print_version.unwrap();
print_version_fn(device, libc::fdopen(libc::STDOUT_FILENO, mode.as_ptr()));
}
}
/// Update CSV contacts database.
pub fn write_csv(radio: &Radio, filename: &str) {
let device = radio.ptr;
let filename = CString::new(filename.to_string()).unwrap();
unsafe {
radio_write_csv(device, filename.as_ptr());
}
}
/// 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;
}