Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c1913d9943 | ||
|
b76a115054 | ||
|
c56fa32b2c | ||
|
963dc072c9 | ||
|
23fc703abd | ||
|
9015d9f8e0 | ||
|
fc45f65a52 | ||
|
fc15b1d1db | ||
|
3f4b18368b | ||
|
5658759cf3 | ||
|
0acf55a971 | ||
|
8430209828 | ||
|
f7aa60df21 | ||
|
00570e0e91 | ||
|
fed7cea057 | ||
|
98eaa63740 | ||
|
7a39fd1ae2 | ||
|
ad5b0547e4 | ||
|
4f082277d8 | ||
|
7793870f59 | ||
|
ea9ff0a534 | ||
|
c921cc54d6 | ||
|
ca27d4035f | ||
|
8fd7e82951 | ||
|
25571023a3 | ||
|
61544fd23f | ||
|
cccbee30a8 | ||
|
2a7c32924c | ||
|
b1116daba2 | ||
|
7fbcba54fd | ||
|
8f544cfa95 | ||
|
e8099fc681 | ||
|
cece963e91 | ||
|
2315ee4a74 | ||
|
c2471516fd | ||
|
05b7842053 | ||
|
863f14b0c2 | ||
|
cd74a703c1 | ||
|
4183e0afc5 | ||
|
48faf189c8 | ||
|
48552c64f6 | ||
|
24d7a6b495 | ||
|
c44e3e15c5 | ||
|
d41d257650 | ||
|
1c81834b62 | ||
|
88e8fb48d8 | ||
|
c7c746a383 | ||
|
2a91efd07f | ||
|
61a142d570 | ||
|
b64211f27c | ||
|
d55f8fd5ed |
6
.gitignore
vendored
6
.gitignore
vendored
@ -2,3 +2,9 @@
|
|||||||
*.hd
|
*.hd
|
||||||
dmrconfig
|
dmrconfig
|
||||||
dmrconfig.exe
|
dmrconfig.exe
|
||||||
|
|
||||||
|
|
||||||
|
# Added by cargo
|
||||||
|
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
|
19
Cargo.toml
Normal file
19
Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "dmrconfig"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Greg Shuflin <greg.shuflin@protonmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libc = "0.2"
|
||||||
|
getopts = "0.2"
|
||||||
|
chrono = "0.4"
|
||||||
|
serial = "0.4"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
bindgen = "0.53.1"
|
15
Makefile
15
Makefile
@ -3,8 +3,8 @@ CC ?= gcc
|
|||||||
VERSION = $(shell git describe --tags --abbrev=0)
|
VERSION = $(shell git describe --tags --abbrev=0)
|
||||||
GITCOUNT = $(shell git rev-list HEAD --count)
|
GITCOUNT = $(shell git rev-list HEAD --count)
|
||||||
UNAME = $(shell uname)
|
UNAME = $(shell uname)
|
||||||
|
VERSION_STR = '$(VERSION).$(GITCOUNT)'
|
||||||
OBJS = main.o util.o radio.o dfu-libusb.o uv380.o md380.o rd5r.o \
|
OBJS = main.o util.o dfu-libusb.o uv380.o md380.o rd5r.o \
|
||||||
gd77.o hid.o serial.o d868uv.o dm1801.o
|
gd77.o hid.o serial.o d868uv.o dm1801.o
|
||||||
CFLAGS ?= -g -O -Wall -Werror
|
CFLAGS ?= -g -O -Wall -Werror
|
||||||
CFLAGS += -DVERSION='"$(VERSION).$(GITCOUNT)"' \
|
CFLAGS += -DVERSION='"$(VERSION).$(GITCOUNT)"' \
|
||||||
@ -12,6 +12,8 @@ CFLAGS += -DVERSION='"$(VERSION).$(GITCOUNT)"' \
|
|||||||
LDFLAGS ?= -g
|
LDFLAGS ?= -g
|
||||||
LIBS = $(shell pkg-config --libs --static libusb-1.0)
|
LIBS = $(shell pkg-config --libs --static libusb-1.0)
|
||||||
|
|
||||||
|
RUST_RELEASE := target/release/libdmrconfig.a
|
||||||
|
|
||||||
#
|
#
|
||||||
# Make sure pkg-config is installed.
|
# Make sure pkg-config is installed.
|
||||||
#
|
#
|
||||||
@ -48,8 +50,8 @@ endif
|
|||||||
|
|
||||||
all: dmrconfig
|
all: dmrconfig
|
||||||
|
|
||||||
dmrconfig: $(OBJS)
|
dmrconfig: $(OBJS) $(RUST_RELEASE)
|
||||||
$(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
|
$(CC) $(LDFLAGS) -o $@ $(OBJS) $(RUST_RELEASE) -ldl $(LIBS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f *~ *.o core dmrconfig dmrconfig.exe
|
rm -f *~ *.o core dmrconfig dmrconfig.exe
|
||||||
@ -57,6 +59,10 @@ clean:
|
|||||||
install: dmrconfig
|
install: dmrconfig
|
||||||
install -c -s dmrconfig /usr/local/bin/dmrconfig
|
install -c -s dmrconfig /usr/local/bin/dmrconfig
|
||||||
|
|
||||||
|
.PHONY: $(RUST_RELEASE)
|
||||||
|
target/release/libdmrconfig.a:
|
||||||
|
VERSION=$(VERSION_STR) cargo build --verbose --release
|
||||||
|
|
||||||
###
|
###
|
||||||
d868uv.o: d868uv.c radio.h util.h d868uv-map.h
|
d868uv.o: d868uv.c radio.h util.h d868uv-map.h
|
||||||
dfu-libusb.o: dfu-libusb.c util.h
|
dfu-libusb.o: dfu-libusb.c util.h
|
||||||
@ -68,7 +74,6 @@ hid-macos.o: hid-macos.c util.h
|
|||||||
hid-windows.o: hid-windows.c util.h
|
hid-windows.o: hid-windows.c util.h
|
||||||
main.o: main.c radio.h util.h
|
main.o: main.c radio.h util.h
|
||||||
md380.o: md380.c radio.h util.h
|
md380.o: md380.c radio.h util.h
|
||||||
radio.o: radio.c radio.h util.h
|
|
||||||
rd5r.o: rd5r.c radio.h util.h
|
rd5r.o: rd5r.c radio.h util.h
|
||||||
serial.o: serial.c util.h
|
serial.o: serial.c util.h
|
||||||
util.o: util.c util.h
|
util.o: util.c util.h
|
||||||
|
49
build.rs
Normal file
49
build.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
extern crate bindgen;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Tell cargo to invalidate the built crate whenever the wrapper changes
|
||||||
|
println!("cargo:rerun-if-changed=radio.h");
|
||||||
|
|
||||||
|
// The bindgen::Builder is the main entry point
|
||||||
|
// to bindgen, and lets you build up options for
|
||||||
|
// the resulting bindings.
|
||||||
|
let bindings = bindgen::Builder::default()
|
||||||
|
// The input header we would like to generate
|
||||||
|
// bindings for.
|
||||||
|
.header("radio.h")
|
||||||
|
// Tell cargo to invalidate the built crate whenever any of the
|
||||||
|
// included header files changed.
|
||||||
|
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
|
||||||
|
.whitelist_type("radio_device_t")
|
||||||
|
.whitelist_var("radio_md380")
|
||||||
|
.whitelist_var("radio_md390")
|
||||||
|
.whitelist_var("radio_uv380")
|
||||||
|
.whitelist_var("radio_uv390")
|
||||||
|
.whitelist_var("radio_md2017")
|
||||||
|
.whitelist_var("radio_md9600")
|
||||||
|
.whitelist_var("radio_rd5r")
|
||||||
|
.whitelist_var("radio_dm1801")
|
||||||
|
.whitelist_var("radio_rt84")
|
||||||
|
.whitelist_var("radio_gd77")
|
||||||
|
.whitelist_var("radio_d868uv")
|
||||||
|
.whitelist_var("radio_d878uv")
|
||||||
|
.whitelist_var("radio_dmr6x2")
|
||||||
|
.whitelist_var("radio_d900")
|
||||||
|
.whitelist_var("radio_dp880")
|
||||||
|
.whitelist_var("radio_rt27d")
|
||||||
|
.blacklist_type("FILE")
|
||||||
|
// Finish the builder and generate the bindings.
|
||||||
|
.generate()
|
||||||
|
// Unwrap the Result and panic on failure.
|
||||||
|
.expect("Unable to generate bindings for radio.h");
|
||||||
|
|
||||||
|
// Write the bindings to the $OUT_DIR/bindings.rs file.
|
||||||
|
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||||
|
bindings
|
||||||
|
.write_to_file(out_path.join("bindings.rs"))
|
||||||
|
.expect("Couldn't write bindings!");
|
||||||
|
}
|
@ -347,7 +347,7 @@ static const char *identify()
|
|||||||
// Return the first path found (dynamically allocated).
|
// Return the first path found (dynamically allocated).
|
||||||
// Return 0 when no device with such GUID is present.
|
// Return 0 when no device with such GUID is present.
|
||||||
//
|
//
|
||||||
static char *find_path(GUID *guid)
|
static char *find_path_for_guid(GUID *guid)
|
||||||
{
|
{
|
||||||
char *path = 0;
|
char *path = 0;
|
||||||
|
|
||||||
@ -392,7 +392,7 @@ const char *dfu_init(unsigned vid, unsigned pid)
|
|||||||
|
|
||||||
// Find path for device.
|
// Find path for device.
|
||||||
if (vid == 0x0483 && pid == 0xdf11) {
|
if (vid == 0x0483 && pid == 0xdf11) {
|
||||||
path = find_path(&guid_0483_df11);
|
path = find_path_for_guid(&guid_0483_df11);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "No guid for vid=%04x, pid=%04x!\n", vid, pid);
|
fprintf(stderr, "No guid for vid=%04x, pid=%04x!\n", vid, pid);
|
||||||
exit(-1);
|
exit(-1);
|
||||||
|
2
dm1801.c
2
dm1801.c
@ -348,6 +348,7 @@ static void dm1801_print_version(radio_device_t *radio, FILE *out)
|
|||||||
static void download(radio_device_t *radio)
|
static void download(radio_device_t *radio)
|
||||||
{
|
{
|
||||||
int bno;
|
int bno;
|
||||||
|
int radio_progress = 0;
|
||||||
|
|
||||||
// Read range 0x80...0x1ee5f.
|
// Read range 0x80...0x1ee5f.
|
||||||
#define NBLK 989
|
#define NBLK 989
|
||||||
@ -390,6 +391,7 @@ static void dm1801_download(radio_device_t *radio)
|
|||||||
static void dm1801_upload(radio_device_t *radio, int cont_flag)
|
static void dm1801_upload(radio_device_t *radio, int cont_flag)
|
||||||
{
|
{
|
||||||
int bno;
|
int bno;
|
||||||
|
int radio_progress = 0;
|
||||||
|
|
||||||
// Write range 0x80...0x1ee5f.
|
// Write range 0x80...0x1ee5f.
|
||||||
for (bno = 1; bno < NBLK; bno++) {
|
for (bno = 1; bno < NBLK; bno++) {
|
||||||
|
2
gd77.c
2
gd77.c
@ -348,6 +348,7 @@ static void gd77_print_version(radio_device_t *radio, FILE *out)
|
|||||||
static void download(radio_device_t *radio)
|
static void download(radio_device_t *radio)
|
||||||
{
|
{
|
||||||
int bno;
|
int bno;
|
||||||
|
int radio_progress = 0;
|
||||||
|
|
||||||
// Read range 0x80...0x1e29f.
|
// Read range 0x80...0x1e29f.
|
||||||
for (bno=1; bno<966; bno++) {
|
for (bno=1; bno<966; bno++) {
|
||||||
@ -388,6 +389,7 @@ static void gd77_download(radio_device_t *radio)
|
|||||||
static void gd77_upload(radio_device_t *radio, int cont_flag)
|
static void gd77_upload(radio_device_t *radio, int cont_flag)
|
||||||
{
|
{
|
||||||
int bno;
|
int bno;
|
||||||
|
int radio_progress = 0;
|
||||||
|
|
||||||
// Write range 0x80...0x1e29f.
|
// Write range 0x80...0x1e29f.
|
||||||
for (bno=1; bno<966; bno++) {
|
for (bno=1; bno<966; bno++) {
|
||||||
|
158
main.c
158
main.c
@ -32,163 +32,15 @@
|
|||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
const char version[] = VERSION;
|
const char version[] = VERSION;
|
||||||
const char *copyright;
|
|
||||||
|
|
||||||
extern char *optarg;
|
extern char *optarg;
|
||||||
extern int optind;
|
extern int optind;
|
||||||
|
|
||||||
|
extern int rust_main(int argc, char** argv);
|
||||||
|
|
||||||
|
//TODO handle this correctly
|
||||||
int trace_flag = 0;
|
int trace_flag = 0;
|
||||||
|
|
||||||
void usage()
|
int main(int argc, char **argv) {
|
||||||
{
|
return rust_main(argc, argv);
|
||||||
fprintf(stderr, "DMR Config, Version %s, %s\n", version, copyright);
|
|
||||||
fprintf(stderr, "Usage:\n");
|
|
||||||
fprintf(stderr, " dmrconfig -r [-t]\n");
|
|
||||||
fprintf(stderr, " Read codeplug from the radio to a file 'device.img'.\n");
|
|
||||||
fprintf(stderr, " Save configuration to a text file 'device.conf'.\n");
|
|
||||||
fprintf(stderr, " dmrconfig -w [-t] file.img\n");
|
|
||||||
fprintf(stderr, " Write codeplug to the radio.\n");
|
|
||||||
fprintf(stderr, " dmrconfig -v [-t] file.conf\n");
|
|
||||||
fprintf(stderr, " Verify configuration script for the radio.\n");
|
|
||||||
fprintf(stderr, " dmrconfig -c [-t] file.conf\n");
|
|
||||||
fprintf(stderr, " Apply configuration script to the radio.\n");
|
|
||||||
fprintf(stderr, " dmrconfig -c file.img file.conf\n");
|
|
||||||
fprintf(stderr, " Apply configuration script to the codeplug image.\n");
|
|
||||||
fprintf(stderr, " Store modified copy to a file 'device.img'.\n");
|
|
||||||
fprintf(stderr, " dmrconfig file.img\n");
|
|
||||||
fprintf(stderr, " Display configuration from the codeplug image.\n");
|
|
||||||
fprintf(stderr, " dmrconfig -u [-t] file.csv\n");
|
|
||||||
fprintf(stderr, " Update contacts database from CSV file.\n");
|
|
||||||
fprintf(stderr, "Options:\n");
|
|
||||||
fprintf(stderr, " -r Read codeplug from the radio.\n");
|
|
||||||
fprintf(stderr, " -w Write codeplug to the radio.\n");
|
|
||||||
fprintf(stderr, " -c Configure the radio from a text script.\n");
|
|
||||||
fprintf(stderr, " -v Verify config file.\n");
|
|
||||||
fprintf(stderr, " -u Update contacts database.\n");
|
|
||||||
fprintf(stderr, " -l List all supported radios.\n");
|
|
||||||
fprintf(stderr, " -t Trace USB protocol.\n");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
int read_flag = 0, write_flag = 0, config_flag = 0, csv_flag = 0;
|
|
||||||
int list_flag = 0, verify_flag = 0;
|
|
||||||
|
|
||||||
copyright = "Copyright (C) 2018 Serge Vakulenko KK6ABQ";
|
|
||||||
trace_flag = 0;
|
|
||||||
for (;;) {
|
|
||||||
switch (getopt(argc, argv, "tcwrulv")) {
|
|
||||||
case 't': ++trace_flag; continue;
|
|
||||||
case 'r': ++read_flag; continue;
|
|
||||||
case 'w': ++write_flag; continue;
|
|
||||||
case 'c': ++config_flag; continue;
|
|
||||||
case 'u': ++csv_flag; continue;
|
|
||||||
case 'l': ++list_flag; continue;
|
|
||||||
case 'v': ++verify_flag; continue;
|
|
||||||
default:
|
|
||||||
usage();
|
|
||||||
case EOF:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
argc -= optind;
|
|
||||||
argv += optind;
|
|
||||||
if (list_flag) {
|
|
||||||
radio_list();
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
if (read_flag + write_flag + config_flag + csv_flag + verify_flag > 1) {
|
|
||||||
fprintf(stderr, "Only one of -r, -w, -c, -v or -u options is allowed.\n");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
setvbuf(stdout, 0, _IOLBF, 0);
|
|
||||||
setvbuf(stderr, 0, _IOLBF, 0);
|
|
||||||
|
|
||||||
if (write_flag) {
|
|
||||||
// Restore image file to device.
|
|
||||||
if (argc != 1)
|
|
||||||
usage();
|
|
||||||
|
|
||||||
radio_connect();
|
|
||||||
radio_read_image(argv[0]);
|
|
||||||
radio_print_version(stdout);
|
|
||||||
radio_upload(0);
|
|
||||||
radio_disconnect();
|
|
||||||
|
|
||||||
} else if (config_flag) {
|
|
||||||
if (argc != 1 && argc != 2)
|
|
||||||
usage();
|
|
||||||
|
|
||||||
if (argc == 2) {
|
|
||||||
// Apply text config to image file.
|
|
||||||
radio_read_image(argv[0]);
|
|
||||||
radio_print_version(stdout);
|
|
||||||
radio_parse_config(argv[1]);
|
|
||||||
radio_verify_config();
|
|
||||||
radio_save_image("device.img");
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Update device from text config file.
|
|
||||||
radio_connect();
|
|
||||||
radio_download();
|
|
||||||
radio_print_version(stdout);
|
|
||||||
radio_save_image("backup.img");
|
|
||||||
radio_parse_config(argv[0]);
|
|
||||||
radio_verify_config();
|
|
||||||
radio_upload(1);
|
|
||||||
radio_disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (verify_flag) {
|
|
||||||
if (argc != 1)
|
|
||||||
usage();
|
|
||||||
|
|
||||||
// Verify text config file.
|
|
||||||
radio_connect();
|
|
||||||
radio_parse_config(argv[0]);
|
|
||||||
radio_verify_config();
|
|
||||||
radio_disconnect();
|
|
||||||
|
|
||||||
} else if (read_flag) {
|
|
||||||
if (argc != 0)
|
|
||||||
usage();
|
|
||||||
|
|
||||||
// Dump device to image file.
|
|
||||||
radio_connect();
|
|
||||||
radio_download();
|
|
||||||
radio_print_version(stdout);
|
|
||||||
radio_disconnect();
|
|
||||||
radio_save_image("device.img");
|
|
||||||
|
|
||||||
// Print configuration to file.
|
|
||||||
const char *filename = "device.conf";
|
|
||||||
printf("Print configuration to file '%s'.\n", filename);
|
|
||||||
FILE *conf = fopen(filename, "w");
|
|
||||||
if (!conf) {
|
|
||||||
perror(filename);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
radio_print_config(conf, 1);
|
|
||||||
fclose(conf);
|
|
||||||
|
|
||||||
} else if (csv_flag) {
|
|
||||||
// Update contacts database on the device.
|
|
||||||
if (argc != 1)
|
|
||||||
usage();
|
|
||||||
|
|
||||||
radio_connect();
|
|
||||||
radio_write_csv(argv[0]);
|
|
||||||
radio_disconnect();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (argc != 1)
|
|
||||||
usage();
|
|
||||||
|
|
||||||
// Print configuration from image file.
|
|
||||||
radio_read_image(argv[0]);
|
|
||||||
radio_print_config(stdout, !isatty(1));
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
2
md380.c
2
md380.c
@ -365,6 +365,7 @@ static void md380_print_version(radio_device_t *radio, FILE *out)
|
|||||||
static void md380_download(radio_device_t *radio)
|
static void md380_download(radio_device_t *radio)
|
||||||
{
|
{
|
||||||
int bno;
|
int bno;
|
||||||
|
int radio_progress = 0;
|
||||||
|
|
||||||
for (bno=0; bno<MEMSZ/1024; bno++) {
|
for (bno=0; bno<MEMSZ/1024; bno++) {
|
||||||
dfu_read_block(bno, &radio_mem[bno*1024], 1024);
|
dfu_read_block(bno, &radio_mem[bno*1024], 1024);
|
||||||
@ -383,6 +384,7 @@ static void md380_download(radio_device_t *radio)
|
|||||||
static void md380_upload(radio_device_t *radio, int cont_flag)
|
static void md380_upload(radio_device_t *radio, int cont_flag)
|
||||||
{
|
{
|
||||||
int bno;
|
int bno;
|
||||||
|
int radio_progress = 0;
|
||||||
|
|
||||||
dfu_erase(0, MEMSZ);
|
dfu_erase(0, MEMSZ);
|
||||||
|
|
||||||
|
428
radio.c
428
radio.c
@ -1,428 +0,0 @@
|
|||||||
/*
|
|
||||||
* Configuration Utility for DMR radios.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2018 Serge Vakulenko, KK6ABQ
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
* 3. The name of the author may not be used to endorse or promote products
|
|
||||||
* derived from this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
||||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
||||||
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
||||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
||||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
||||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
||||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include "radio.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
static struct {
|
|
||||||
char *ident;
|
|
||||||
radio_device_t *device;
|
|
||||||
} radio_tab[] = {
|
|
||||||
{ "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
|
|
||||||
{ 0, 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
unsigned char radio_mem [1024*1024*2]; // Radio memory contents, up to 2 Mbytes
|
|
||||||
int radio_progress; // Read/write progress counter
|
|
||||||
|
|
||||||
static radio_device_t *device; // Device-dependent interface
|
|
||||||
|
|
||||||
//
|
|
||||||
// Close the serial port.
|
|
||||||
//
|
|
||||||
void radio_disconnect()
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Close device.\n");
|
|
||||||
|
|
||||||
// Restore the normal radio mode.
|
|
||||||
dfu_reboot();
|
|
||||||
dfu_close();
|
|
||||||
hid_close();
|
|
||||||
serial_close();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Print a generic information about the device.
|
|
||||||
//
|
|
||||||
void radio_print_version(FILE *out)
|
|
||||||
{
|
|
||||||
device->print_version(device, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Connect to the radio and identify the type of device.
|
|
||||||
//
|
|
||||||
void radio_connect()
|
|
||||||
{
|
|
||||||
const char *ident;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
// Try TYT MD family.
|
|
||||||
ident = dfu_init(0x0483, 0xdf11);
|
|
||||||
if (! ident) {
|
|
||||||
// Try RD-5R, DM-1801 and GD-77.
|
|
||||||
if (hid_init(0x15a2, 0x0073) >= 0)
|
|
||||||
ident = hid_identify();
|
|
||||||
}
|
|
||||||
if (! ident) {
|
|
||||||
// Try AT-D868UV.
|
|
||||||
if (serial_init(0x28e9, 0x018a) >= 0)
|
|
||||||
ident = serial_identify();
|
|
||||||
}
|
|
||||||
if (! ident) {
|
|
||||||
fprintf(stderr, "No radio detected.\n");
|
|
||||||
fprintf(stderr, "Check your USB cable!\n");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i=0; radio_tab[i].ident; i++) {
|
|
||||||
if (strcasecmp(ident, radio_tab[i].ident) == 0) {
|
|
||||||
device = radio_tab[i].device;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (! device) {
|
|
||||||
fprintf(stderr, "Unrecognized radio '%s'.\n", ident);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
fprintf(stderr, "Connect to %s.\n", device->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// List all supported radios.
|
|
||||||
//
|
|
||||||
void radio_list()
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
printf("Supported radios:\n");
|
|
||||||
for (i=0; radio_tab[i].ident; i++) {
|
|
||||||
printf(" %s\n", radio_tab[i].device->name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Read firmware image from the device.
|
|
||||||
//
|
|
||||||
void radio_download()
|
|
||||||
{
|
|
||||||
radio_progress = 0;
|
|
||||||
if (! trace_flag) {
|
|
||||||
fprintf(stderr, "Read device: ");
|
|
||||||
fflush(stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
device->download(device);
|
|
||||||
|
|
||||||
if (! trace_flag)
|
|
||||||
fprintf(stderr, " done.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Write firmware image to the device.
|
|
||||||
//
|
|
||||||
void radio_upload(int cont_flag)
|
|
||||||
{
|
|
||||||
// Check for compatibility.
|
|
||||||
if (! device->is_compatible(device)) {
|
|
||||||
fprintf(stderr, "Incompatible image - cannot upload.\n");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
radio_progress = 0;
|
|
||||||
if (! trace_flag) {
|
|
||||||
fprintf(stderr, "Write device: ");
|
|
||||||
fflush(stderr);
|
|
||||||
}
|
|
||||||
device->upload(device, cont_flag);
|
|
||||||
|
|
||||||
if (! trace_flag)
|
|
||||||
fprintf(stderr, " done.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Read firmware image from the binary file.
|
|
||||||
//
|
|
||||||
void radio_read_image(const char *filename)
|
|
||||||
{
|
|
||||||
FILE *img;
|
|
||||||
struct stat st;
|
|
||||||
char ident[8];
|
|
||||||
|
|
||||||
fprintf(stderr, "Read codeplug from file '%s'.\n", filename);
|
|
||||||
img = fopen(filename, "rb");
|
|
||||||
if (! img) {
|
|
||||||
perror(filename);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Guess device type by file size.
|
|
||||||
if (stat(filename, &st) < 0) {
|
|
||||||
perror(filename);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
switch (st.st_size) {
|
|
||||||
case 851968:
|
|
||||||
case 852533:
|
|
||||||
device = &radio_uv380;
|
|
||||||
break;
|
|
||||||
case 262144:
|
|
||||||
case 262709:
|
|
||||||
device = &radio_md380;
|
|
||||||
break;
|
|
||||||
case 1606528:
|
|
||||||
if (fread(ident, 1, 8, img) != 8) {
|
|
||||||
fprintf(stderr, "%s: Cannot read header.\n", filename);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
fseek(img, 0, SEEK_SET);
|
|
||||||
if (memcmp(ident, "D868UVE", 7) == 0) {
|
|
||||||
device = &radio_d868uv;
|
|
||||||
} else if (memcmp(ident, "D878UV", 6) == 0) {
|
|
||||||
device = &radio_d878uv;
|
|
||||||
} else if (memcmp(ident, "D6X2UV", 6) == 0) {
|
|
||||||
device = &radio_dmr6x2;
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "%s: Unrecognized header '%.6s'\n",
|
|
||||||
filename, ident);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 131072:
|
|
||||||
if (fread(ident, 1, 8, img) != 8) {
|
|
||||||
fprintf(stderr, "%s: Cannot read header.\n", filename);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
if (memcmp(ident, "BF-5R", 5) == 0) {
|
|
||||||
device = &radio_rd5r;
|
|
||||||
} else if (memcmp(ident, "MD-760P", 7) == 0) {
|
|
||||||
device = &radio_gd77;
|
|
||||||
} else if (memcmp(ident, "1801", 4) == 0) {
|
|
||||||
device = &radio_dm1801;
|
|
||||||
} else if (memcmp(ident, "MD-760", 6) == 0) {
|
|
||||||
fprintf(stderr, "Old Radioddity GD-77 v2.6 image not supported!\n");
|
|
||||||
exit(-1);
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "%s: Unrecognized header '%.6s'\n",
|
|
||||||
filename, ident);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
fseek(img, 0, SEEK_SET);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fprintf(stderr, "%s: Unrecognized file size %u bytes.\n",
|
|
||||||
filename, (int) st.st_size);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
device->read_image(device, img);
|
|
||||||
fclose(img);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Save firmware image to the binary file.
|
|
||||||
//
|
|
||||||
void radio_save_image(const char *filename)
|
|
||||||
{
|
|
||||||
FILE *img;
|
|
||||||
|
|
||||||
fprintf(stderr, "Write codeplug to file '%s'.\n", filename);
|
|
||||||
img = fopen(filename, "wb");
|
|
||||||
if (! img) {
|
|
||||||
perror(filename);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
device->save_image(device, img);
|
|
||||||
fclose(img);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Read the configuration from text file, and modify the firmware.
|
|
||||||
//
|
|
||||||
void radio_parse_config(const char *filename)
|
|
||||||
{
|
|
||||||
FILE *conf;
|
|
||||||
char line [256], *p, *v;
|
|
||||||
int table_id = 0, table_dirty = 0;
|
|
||||||
|
|
||||||
fprintf(stderr, "Read configuration from file '%s'.\n", filename);
|
|
||||||
conf = fopen(filename, "r");
|
|
||||||
if (! conf) {
|
|
||||||
perror(filename);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
device->channel_count = 0;
|
|
||||||
while (fgets(line, sizeof(line), conf)) {
|
|
||||||
line[sizeof(line)-1] = 0;
|
|
||||||
|
|
||||||
// Strip comments.
|
|
||||||
v = strchr(line, '#');
|
|
||||||
if (v)
|
|
||||||
*v = 0;
|
|
||||||
|
|
||||||
// Strip trailing spaces and newline.
|
|
||||||
v = line + strlen(line) - 1;
|
|
||||||
while (v >= line && (*v=='\n' || *v=='\r' || *v==' ' || *v=='\t'))
|
|
||||||
*v-- = 0;
|
|
||||||
|
|
||||||
// Ignore comments and empty lines.
|
|
||||||
p = line;
|
|
||||||
if (*p == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (*p != ' ') {
|
|
||||||
// Table finished.
|
|
||||||
table_id = 0;
|
|
||||||
|
|
||||||
// Find the value.
|
|
||||||
v = strchr(p, ':');
|
|
||||||
if (! v) {
|
|
||||||
// Table header: get table type.
|
|
||||||
table_id = device->parse_header(device, p);
|
|
||||||
if (! table_id) {
|
|
||||||
badline: fprintf(stderr, "Invalid line: '%s'\n", line);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
table_dirty = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parameter.
|
|
||||||
*v++ = 0;
|
|
||||||
|
|
||||||
// Skip spaces.
|
|
||||||
while (*v == ' ' || *v == '\t')
|
|
||||||
v++;
|
|
||||||
|
|
||||||
device->parse_parameter(device, p, v);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Table row or comment.
|
|
||||||
// Skip spaces.
|
|
||||||
// Ignore comments and empty lines.
|
|
||||||
while (*p == ' ' || *p == '\t')
|
|
||||||
p++;
|
|
||||||
if (*p == '#' || *p == 0)
|
|
||||||
continue;
|
|
||||||
if (! table_id) {
|
|
||||||
goto badline;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! device->parse_row(device, table_id, ! table_dirty, p)) {
|
|
||||||
goto badline;
|
|
||||||
}
|
|
||||||
table_dirty = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fclose(conf);
|
|
||||||
device->update_timestamp(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Print full information about the device configuration.
|
|
||||||
//
|
|
||||||
void radio_print_config(FILE *out, int verbose)
|
|
||||||
{
|
|
||||||
if (verbose) {
|
|
||||||
char buf [40];
|
|
||||||
time_t t;
|
|
||||||
struct tm *tmp;
|
|
||||||
|
|
||||||
t = time(NULL);
|
|
||||||
tmp = localtime(&t);
|
|
||||||
if (! tmp || ! strftime(buf, sizeof(buf), "%Y/%m/%d ", tmp))
|
|
||||||
buf[0] = 0;
|
|
||||||
fprintf(out, "#\n");
|
|
||||||
fprintf(out, "# Configuration generated %sby dmrconfig, version %s\n",
|
|
||||||
buf, version);
|
|
||||||
fprintf(out, "#\n");
|
|
||||||
}
|
|
||||||
device->print_config(device, out, verbose);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Check the configuration is correct.
|
|
||||||
//
|
|
||||||
void radio_verify_config()
|
|
||||||
{
|
|
||||||
if (!device->verify_config(device)) {
|
|
||||||
// Message should be already printed.
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Update contacts database on the device.
|
|
||||||
//
|
|
||||||
void radio_write_csv(const char *filename)
|
|
||||||
{
|
|
||||||
FILE *csv;
|
|
||||||
|
|
||||||
if (!device->write_csv) {
|
|
||||||
fprintf(stderr, "%s does not support CSV database.\n", device->name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
csv = fopen(filename, "r");
|
|
||||||
if (! csv) {
|
|
||||||
perror(filename);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "Read file '%s'.\n", filename);
|
|
||||||
|
|
||||||
device->write_csv(device, csv);
|
|
||||||
fclose(csv);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Check for compatible radio model.
|
|
||||||
//
|
|
||||||
int radio_is_compatible(const char *name)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i=0; radio_tab[i].ident; i++) {
|
|
||||||
// Radio is compatible when it has the same parse routine.
|
|
||||||
if (device->parse_parameter == radio_tab[i].device->parse_parameter &&
|
|
||||||
strcasecmp(name, radio_tab[i].device->name) == 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
68
radio.h
68
radio.h
@ -26,70 +26,11 @@
|
|||||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//
|
|
||||||
// Connect to the radio via the serial port.
|
|
||||||
// Identify the type of device.
|
|
||||||
//
|
|
||||||
void radio_connect(void);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Close the serial port.
|
|
||||||
//
|
|
||||||
void radio_disconnect(void);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Read firmware image from the device.
|
|
||||||
//
|
|
||||||
void radio_download(void);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Write firmware image to the device.
|
|
||||||
//
|
|
||||||
void radio_upload(int cont_flag);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Print a generic information about the device.
|
|
||||||
//
|
|
||||||
void radio_print_version(FILE *out);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Print full information about the device configuration.
|
|
||||||
//
|
|
||||||
void radio_print_config(FILE *out, int verbose);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Read firmware image from the binary file.
|
|
||||||
//
|
|
||||||
void radio_read_image(const char *filename);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Save firmware image to the binary file.
|
|
||||||
//
|
|
||||||
void radio_save_image(const char *filename);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Read the configuration from text file, and modify the firmware.
|
|
||||||
//
|
|
||||||
void radio_parse_config(const char *filename);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Check the configuration.
|
|
||||||
//
|
|
||||||
void radio_verify_config(void);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Update CSV contacts database.
|
|
||||||
//
|
|
||||||
void radio_write_csv(const char *filename);
|
|
||||||
|
|
||||||
//
|
|
||||||
// List all supported radios.
|
|
||||||
//
|
|
||||||
void radio_list(void);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Check for compatible radio model.
|
// Check for compatible radio model.
|
||||||
//
|
//
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
int radio_is_compatible(const char *ident);
|
int radio_is_compatible(const char *ident);
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -140,8 +81,3 @@ extern unsigned char radio_mem[];
|
|||||||
// File descriptor of serial port with programming cable attached.
|
// File descriptor of serial port with programming cable attached.
|
||||||
//
|
//
|
||||||
extern int radio_port;
|
extern int radio_port;
|
||||||
|
|
||||||
//
|
|
||||||
// Read/write progress counter.
|
|
||||||
//
|
|
||||||
extern int radio_progress;
|
|
||||||
|
2
rd5r.c
2
rd5r.c
@ -345,6 +345,7 @@ static void rd5r_print_version(radio_device_t *radio, FILE *out)
|
|||||||
static void download(radio_device_t *radio)
|
static void download(radio_device_t *radio)
|
||||||
{
|
{
|
||||||
int bno;
|
int bno;
|
||||||
|
int radio_progress = 0;
|
||||||
|
|
||||||
// Read range 0x80...0x1e29f.
|
// Read range 0x80...0x1e29f.
|
||||||
for (bno=1; bno<966; bno++) {
|
for (bno=1; bno<966; bno++) {
|
||||||
@ -385,6 +386,7 @@ static void rd5r_download(radio_device_t *radio)
|
|||||||
static void rd5r_upload(radio_device_t *radio, int cont_flag)
|
static void rd5r_upload(radio_device_t *radio, int cont_flag)
|
||||||
{
|
{
|
||||||
int bno;
|
int bno;
|
||||||
|
int radio_progress = 0;
|
||||||
|
|
||||||
// Write range 0x80...0x1e29f.
|
// Write range 0x80...0x1e29f.
|
||||||
for (bno=1; bno<966; bno++) {
|
for (bno=1; bno<966; bno++) {
|
||||||
|
28
serial.c
28
serial.c
@ -55,7 +55,7 @@
|
|||||||
# include <IOKit/serial/IOSerialKeys.h>
|
# include <IOKit/serial/IOSerialKeys.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static char *dev_path;
|
//static char *dev_path;
|
||||||
|
|
||||||
static const unsigned char CMD_PRG[] = "PROGRAM";
|
static const unsigned char CMD_PRG[] = "PROGRAM";
|
||||||
static const unsigned char CMD_PRG2[] = "\2";
|
static const unsigned char CMD_PRG2[] = "\2";
|
||||||
@ -357,7 +357,7 @@ int serial_open(const char *devname, int baud_rate)
|
|||||||
//
|
//
|
||||||
// Find a device path by vid/pid.
|
// Find a device path by vid/pid.
|
||||||
//
|
//
|
||||||
static char *find_path(int vid, int pid)
|
char *find_path(int vid, int pid)
|
||||||
{
|
{
|
||||||
char *result = 0;
|
char *result = 0;
|
||||||
|
|
||||||
@ -576,21 +576,6 @@ static char *find_path(int vid, int pid)
|
|||||||
// Connect to the specified device.
|
// Connect to the specified device.
|
||||||
// Initiate the programming session.
|
// Initiate the programming session.
|
||||||
//
|
//
|
||||||
int serial_init(int vid, int pid)
|
|
||||||
{
|
|
||||||
dev_path = find_path(vid, pid);
|
|
||||||
if (!dev_path) {
|
|
||||||
if (trace_flag) {
|
|
||||||
fprintf(stderr, "Cannot find USB device %04x:%04x\n",
|
|
||||||
vid, pid);
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Succeeded.
|
|
||||||
printf("Serial port: %s\n", dev_path);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Send the command sequence and get back a response.
|
// Send the command sequence and get back a response.
|
||||||
@ -612,7 +597,8 @@ static int send_recv(const unsigned char *cmd, int cmdlen,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (serial_write(cmd, cmdlen) < 0) {
|
if (serial_write(cmd, cmdlen) < 0) {
|
||||||
fprintf(stderr, "%s: write error\n", dev_path);
|
//fprintf(stderr, "%s: write error\n", dev_path);
|
||||||
|
fprintf(stderr, "serial write error\n");
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -671,7 +657,7 @@ void serial_close()
|
|||||||
// Query and return the device identification string.
|
// Query and return the device identification string.
|
||||||
// On error, return NULL.
|
// On error, return NULL.
|
||||||
//
|
//
|
||||||
const char *serial_identify()
|
const char *serial_identify(char* dev_path)
|
||||||
{
|
{
|
||||||
static unsigned char reply[16];
|
static unsigned char reply[16];
|
||||||
unsigned char ack[3];
|
unsigned char ack[3];
|
||||||
@ -717,7 +703,7 @@ again:
|
|||||||
return (char*)&reply[1];
|
return (char*)&reply[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
void serial_read_region(int addr, unsigned char *data, int nbytes)
|
void c_serial_read_region(int addr, unsigned char *data, int nbytes)
|
||||||
{
|
{
|
||||||
static const int DATASZ = 64;
|
static const int DATASZ = 64;
|
||||||
unsigned char cmd[6], reply[8 + DATASZ];
|
unsigned char cmd[6], reply[8 + DATASZ];
|
||||||
@ -755,7 +741,7 @@ again:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void serial_write_region(int addr, unsigned char *data, int nbytes)
|
void c_serial_write_region(int addr, unsigned char *data, int nbytes)
|
||||||
{
|
{
|
||||||
//static const int DATASZ = 64;
|
//static const int DATASZ = 64;
|
||||||
static const int DATASZ = 16;
|
static const int DATASZ = 16;
|
||||||
|
189
src/lib.rs
Normal file
189
src/lib.rs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
#![allow(non_upper_case_globals)]
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use libc::{c_int, c_char};
|
||||||
|
use getopts::Options;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
mod serial;
|
||||||
|
mod radio;
|
||||||
|
|
||||||
|
const COPYRIGHT: &'static str = "Copyright (C) 2018 Serge Vakulenko KK6ABQ";
|
||||||
|
const VERSION: Option<&'static str> = option_env!("VERSION");
|
||||||
|
|
||||||
|
fn version() -> &'static str {
|
||||||
|
VERSION.unwrap_or("-------")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn print_usage() {
|
||||||
|
let version = version();
|
||||||
|
|
||||||
|
print!("DMR Config, Version {}, {}", version, COPYRIGHT);
|
||||||
|
|
||||||
|
let msg = r#"
|
||||||
|
Usage:
|
||||||
|
dmrconfig -r [-t]
|
||||||
|
Read codeplug from the radio to a file 'device.img'.
|
||||||
|
Save configuration to a text file 'device.conf'.
|
||||||
|
dmrconfig -w [-t] file.img
|
||||||
|
Write codeplug to the radio.
|
||||||
|
dmrconfig -v [-t] file.conf
|
||||||
|
Verify configuration script for the radio.
|
||||||
|
dmrconfig -c [-t] file.conf
|
||||||
|
Apply configuration script to the radio.
|
||||||
|
dmrconfig -c file.img file.conf
|
||||||
|
Apply configuration script to the codeplug image.
|
||||||
|
Store modified copy to a file 'device.img'.
|
||||||
|
dmrconfig file.img
|
||||||
|
Display configuration from the codeplug image.
|
||||||
|
dmrconfig -u [-t] file.csv
|
||||||
|
Update contacts database from CSV file.
|
||||||
|
Options:
|
||||||
|
-r Read codeplug from the radio.
|
||||||
|
-w Write codeplug to the radio.
|
||||||
|
-c Configure the radio from a text script.
|
||||||
|
-v Verify config file.
|
||||||
|
-u Update contacts database.
|
||||||
|
-l List all supported radios.
|
||||||
|
-t Trace USB protocol."#;
|
||||||
|
print!("{}", msg);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_options() -> Options {
|
||||||
|
let mut opts = Options::new();
|
||||||
|
opts.optflag("t", "", "Trace USB protocol.");
|
||||||
|
opts.optflag("r", "", "Read codeplug from the radio to a file 'device.img'.\nSave configuration to a text file 'device.conf'.");
|
||||||
|
opts.optflag("w", "", "Write codeplug to the radio.");
|
||||||
|
opts.optflag("c", "", "Verify configuration script for the radio.");
|
||||||
|
opts.optflag("u", "", "Update contacts database from CSV file.");
|
||||||
|
opts.optflag("l", "", "List all supported radios.");
|
||||||
|
opts.optflag("v", "", "Verify configuration script for the radio.");
|
||||||
|
opts
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn rust_main(_argc: c_int, _argv: *const *const c_char) -> c_int {
|
||||||
|
|
||||||
|
let args = std::env::args().skip(1);
|
||||||
|
let matches = match get_options().parse(args) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(fail) => {
|
||||||
|
eprintln!("{}", fail);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let trace_flag = matches.opt_present("t");
|
||||||
|
|
||||||
|
let list_flag = matches.opt_present("l");
|
||||||
|
let verify_flag = matches.opt_present("v");
|
||||||
|
let read_flag = matches.opt_present("r");
|
||||||
|
let write_flag = matches.opt_present("w");
|
||||||
|
let config_flag = matches.opt_present("c");
|
||||||
|
let csv_flag = matches.opt_present("u");
|
||||||
|
|
||||||
|
if list_flag {
|
||||||
|
radio::list();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if [read_flag, write_flag, config_flag, csv_flag, verify_flag].iter().filter(|x| **x).count() > 1 {
|
||||||
|
eprintln!("Only one of -r, -w, -c, -v or -u options is allowed.");
|
||||||
|
print_usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if write_flag {
|
||||||
|
if matches.free.len() != 1 {
|
||||||
|
print_usage();
|
||||||
|
}
|
||||||
|
let device = radio::connect();
|
||||||
|
radio::read_image(&matches.free[0]);
|
||||||
|
radio::print_version(&device);
|
||||||
|
radio::upload(&device, 0, trace_flag);
|
||||||
|
radio::disconnect();
|
||||||
|
} else if config_flag {
|
||||||
|
let conf_args = matches.free.len();
|
||||||
|
if !(conf_args == 1 || conf_args == 2) {
|
||||||
|
print_usage();
|
||||||
|
}
|
||||||
|
let (config_filename, image_filename) = if conf_args == 2 {
|
||||||
|
(matches.free[1].clone(), Some(matches.free[0].clone()))
|
||||||
|
} else {
|
||||||
|
(matches.free[0].clone(), None)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(img) = image_filename {
|
||||||
|
// Apply text config to image file.
|
||||||
|
let device = radio::connect(); //NOTE this changes the semantics of the program
|
||||||
|
radio::read_image(&img);
|
||||||
|
radio::print_version(&device);
|
||||||
|
radio::parse_config(&device, &config_filename);
|
||||||
|
radio::verify_config(&device);
|
||||||
|
radio::save_image(&device, "device.img");
|
||||||
|
} else {
|
||||||
|
// Update device from text config file.
|
||||||
|
let device = radio::connect();
|
||||||
|
radio::download(&device, trace_flag);
|
||||||
|
radio::print_version(&device);
|
||||||
|
radio::save_image(&device, "device.img");
|
||||||
|
radio::parse_config(&device, &config_filename);
|
||||||
|
radio::verify_config(&device);
|
||||||
|
radio::upload(&device, 1, trace_flag);
|
||||||
|
radio::disconnect();
|
||||||
|
}
|
||||||
|
} else if verify_flag {
|
||||||
|
if matches.free.len() != 1 {
|
||||||
|
print_usage();
|
||||||
|
}
|
||||||
|
// Verify text config file.
|
||||||
|
let device = radio::connect();
|
||||||
|
radio::parse_config(&device, &matches.free[0]);
|
||||||
|
radio::verify_config(&device);
|
||||||
|
radio::disconnect();
|
||||||
|
} else if read_flag {
|
||||||
|
if matches.free.len() != 0 {
|
||||||
|
print_usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump device to image file.
|
||||||
|
let device = radio::connect();
|
||||||
|
radio::download(&device, trace_flag);
|
||||||
|
radio::print_version(&device);
|
||||||
|
radio::disconnect();
|
||||||
|
radio::save_image(&device, "device.img");
|
||||||
|
|
||||||
|
// Print configuration to file.
|
||||||
|
let filename = "device.conf";
|
||||||
|
println!("Print configuration to file '{}.", filename);
|
||||||
|
|
||||||
|
radio::print_config(&device, Some(filename));
|
||||||
|
|
||||||
|
} else if csv_flag {
|
||||||
|
if matches.free.len() != 1 {
|
||||||
|
print_usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
let device = radio::connect();
|
||||||
|
radio::write_csv(&device, &matches.free[0]);
|
||||||
|
radio::disconnect();
|
||||||
|
} else {
|
||||||
|
if matches.free.len() != 1 {
|
||||||
|
print_usage();
|
||||||
|
}
|
||||||
|
let device = radio::read_image(&matches.free[0]);
|
||||||
|
radio::print_config(&device, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(2 + 2, 4);
|
||||||
|
}
|
||||||
|
}
|
512
src/radio.rs
Normal file
512
src/radio.rs
Normal file
@ -0,0 +1,512 @@
|
|||||||
|
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
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Radio memory contents, up to 2 Mbytes
|
||||||
|
#[no_mangle]
|
||||||
|
pub static mut radio_mem: [u8; 1024*1024*2] = [0; 1024*1024*2];
|
||||||
|
|
||||||
|
extern {
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Connect to the radio via the serial port.
|
||||||
|
/// and identify the type of device.
|
||||||
|
pub fn connect() -> Radio {
|
||||||
|
|
||||||
|
let ident_str = (|| {
|
||||||
|
let mut ident: *const c_char;
|
||||||
|
// Try TYT MD family.
|
||||||
|
ident = unsafe { dfu_init(0x0483, 0xdf11) };
|
||||||
|
if ident.is_null() {
|
||||||
|
// Try RD-5R, DM-1801 and GD-77.
|
||||||
|
if unsafe { hid_init(0x15a2, 0x0073) } >= 0 {
|
||||||
|
ident = unsafe { hid_identify() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ident.is_null() {
|
||||||
|
// Try AT-D868UV.
|
||||||
|
let trace_flag = false; //TODO fix
|
||||||
|
if let Some(device_path) = crate::serial::serial_init(0x28e9, 0x018a, trace_flag) {
|
||||||
|
if let Some(identifier) = crate::serial::identify(&device_path) {
|
||||||
|
unsafe {
|
||||||
|
crate::serial::device_path = Some(device_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ident.is_null() {
|
||||||
|
eprintln!("No radio detected.");
|
||||||
|
eprintln!("Check your USB cable!");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { CStr::from_ptr(ident).to_str().unwrap().to_string() }
|
||||||
|
})();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
|
||||||
|
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();
|
||||||
|
crate::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 a text file, and modify the firmware.
|
||||||
|
pub fn parse_config(radio: &Radio, filename: &str) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
update_timestamp_fn(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print full information about the device configuration.
|
||||||
|
/// If `filename` is `None`, write to stdout.
|
||||||
|
pub fn print_config(radio: &Radio, filename: Option<&str>) {
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
None => {
|
||||||
|
unsafe {
|
||||||
|
let verbose = libc::isatty(libc::STDOUT_FILENO) != 1;
|
||||||
|
if verbose {
|
||||||
|
print!("{}", make_config_string());
|
||||||
|
}
|
||||||
|
(libc::STDOUT_FILENO, verbose)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(filename) => {
|
||||||
|
file = std::fs::File::create(filename).unwrap();
|
||||||
|
file.write_all(make_config_string().as_bytes()).unwrap();
|
||||||
|
(file.as_raw_fd(), true)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mode = CString::new("w").unwrap();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let device = radio.ptr as *mut radio_device_t;
|
||||||
|
let print_config_fn = (*device).print_config.unwrap();
|
||||||
|
let file = libc::fdopen(fd, mode.as_ptr());
|
||||||
|
print_config_fn(device, file, if verbose { 1 } else { 0 } );
|
||||||
|
libc::fclose(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 contacts database on the device.
|
||||||
|
pub fn write_csv(radio: &Radio, filename: &str) {
|
||||||
|
let device = radio.ptr as *mut radio_device_t;
|
||||||
|
unsafe {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
259
src/serial.rs
Normal file
259
src/serial.rs
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
use std::ffi::{CStr};
|
||||||
|
use libc::{c_char, c_int, c_uchar, c_void};
|
||||||
|
use std::process::exit;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
//hack to handle the serial device path state
|
||||||
|
pub static mut device_path: Option<String> = None;
|
||||||
|
|
||||||
|
extern {
|
||||||
|
fn find_path(vid: libc::c_int, pid: libc::c_int) -> *const c_char;
|
||||||
|
fn serial_identify(s: *const c_char) -> *const c_char;
|
||||||
|
fn serial_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connect to the specified device.
|
||||||
|
/// Initiate the programming session.
|
||||||
|
pub fn serial_init(vid: u32, pid: u32, trace_flag: bool) -> Option<String> {
|
||||||
|
let dev_path = unsafe { find_path(vid as i32, pid as i32) };
|
||||||
|
|
||||||
|
if dev_path.is_null() {
|
||||||
|
if trace_flag {
|
||||||
|
eprintln!("Cannot find USB device: {:#x}{:#x}", vid, pid);
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dev_path = unsafe { CStr::from_ptr(dev_path).to_str().unwrap().to_string() };
|
||||||
|
println!("Serial port: {}", dev_path);
|
||||||
|
Some(dev_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_receive(port: &mut dyn serial::SerialPort, command: &[u8], reply_len: usize, trace: bool) -> Vec<u8> {
|
||||||
|
if trace {
|
||||||
|
eprintln!("----Send [{}] {:?}", command.len(), command);
|
||||||
|
}
|
||||||
|
|
||||||
|
match port.write(&command) {
|
||||||
|
Ok(n) => {
|
||||||
|
if trace {
|
||||||
|
eprintln!("Wrote {} bytes over serial", n);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Serial write error: {}", e);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buf: Vec<u8> = vec![0; reply_len];
|
||||||
|
match port.read(&mut buf) {
|
||||||
|
Ok(n) if n == reply_len => {
|
||||||
|
if trace {
|
||||||
|
eprintln!("----Received [{}] {:?}", reply_len, buf);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(n) => {
|
||||||
|
eprintln!("Serial: read {} bytes, expected {}", n, reply_len);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Serial read error: {}", e);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn serial_read_region(addr: c_int, data: *mut c_uchar, nbytes: c_int) {
|
||||||
|
use serial::prelude::*;
|
||||||
|
|
||||||
|
let dev_path = unsafe {
|
||||||
|
match device_path {
|
||||||
|
Some(ref s) => s.clone(),
|
||||||
|
None => {
|
||||||
|
eprintln!("No serial device path set, exiting");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut port = match serial::open(&dev_path) {
|
||||||
|
Ok(port) => port,
|
||||||
|
Err(err) => {
|
||||||
|
println!("{}", err);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
port.reconfigure(&|settings: &mut dyn SerialPortSettings| {
|
||||||
|
settings.set_baud_rate(serial::BaudRate::Baud115200)?;
|
||||||
|
settings.set_char_size(serial::CharSize::Bits8);
|
||||||
|
Ok(())
|
||||||
|
}).unwrap();
|
||||||
|
port.set_timeout(Duration::new(1,0)).unwrap();
|
||||||
|
|
||||||
|
const DATA_SIZE: usize = 64;
|
||||||
|
let num_bytes = nbytes as usize;
|
||||||
|
let addr = addr as usize;
|
||||||
|
|
||||||
|
let mut output_data = vec![];
|
||||||
|
|
||||||
|
let mut n = 0;
|
||||||
|
while n < num_bytes {
|
||||||
|
// Serial read command: 'R' aa aa aa aa 0x10
|
||||||
|
let cmd: [u8; 6] = [
|
||||||
|
0x52, // ASCII 'R'
|
||||||
|
((addr + n) >> 24) as u8,
|
||||||
|
((addr + n) >> 16) as u8,
|
||||||
|
((addr + n) >> 8) as u8,
|
||||||
|
(addr + n) as u8,
|
||||||
|
64,
|
||||||
|
];
|
||||||
|
let reply_len = 8 + DATA_SIZE;
|
||||||
|
eprintln!("Here in serial_read_region");
|
||||||
|
let reply = send_receive(&mut port, &cmd, reply_len, true);
|
||||||
|
if reply[0] != b'W' || reply[7+DATA_SIZE] != 0x6 {
|
||||||
|
eprintln!("serial_read_region: wrong read reply: {:?} shit: {}", reply, reply[7+DATA_SIZE]);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Compute checksum
|
||||||
|
let mut checksum: u8 = reply[1];
|
||||||
|
for idx in 2..6+DATA_SIZE {
|
||||||
|
checksum = checksum.overflowing_add(reply[idx]).0;
|
||||||
|
}
|
||||||
|
let expected_checksum = reply[6+DATA_SIZE];
|
||||||
|
if checksum != expected_checksum {
|
||||||
|
eprintln!("serial_read_region: wrong read checksum {}, expected {}", checksum, expected_checksum);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
output_data.extend(reply.into_iter());
|
||||||
|
n += DATA_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
let n = output_data.len();
|
||||||
|
let ptr = output_data.as_ptr() as *const c_void;
|
||||||
|
unsafe {
|
||||||
|
libc::memcpy(data as *mut c_void, ptr, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn serial_write_region(addr: c_int, data: *mut c_uchar, nbytes: c_int) {
|
||||||
|
use serial::prelude::*;
|
||||||
|
|
||||||
|
let dev_path = unsafe {
|
||||||
|
match device_path {
|
||||||
|
Some(ref s) => s.clone(),
|
||||||
|
None => {
|
||||||
|
eprintln!("No serial device path set, exiting");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut port = match serial::open(&dev_path) {
|
||||||
|
Ok(port) => port,
|
||||||
|
Err(err) => {
|
||||||
|
println!("{}", err);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
port.reconfigure(&|settings: &mut dyn SerialPortSettings| {
|
||||||
|
settings.set_baud_rate(serial::BaudRate::Baud115200)?;
|
||||||
|
settings.set_char_size(serial::CharSize::Bits8);
|
||||||
|
Ok(())
|
||||||
|
}).unwrap();
|
||||||
|
port.set_timeout(Duration::new(1,0)).unwrap();
|
||||||
|
|
||||||
|
const DATA_SIZE: usize = 16;
|
||||||
|
|
||||||
|
let num_bytes = nbytes as usize;
|
||||||
|
let addr = addr as usize;
|
||||||
|
|
||||||
|
let mut n = 0;
|
||||||
|
while n < num_bytes {
|
||||||
|
let mut cmd: [u8; 8 + DATA_SIZE] = [0; 8 + DATA_SIZE];
|
||||||
|
cmd[0] = 0x57; // ASCII 'W'
|
||||||
|
cmd[1] = ((addr + n) >> 24) as u8;
|
||||||
|
cmd[2] = ((addr + n) >> 16) as u8;
|
||||||
|
cmd[3] = ((addr + n) >> 8) as u8;
|
||||||
|
cmd[4] = (addr + n) as u8;
|
||||||
|
cmd[5] = DATA_SIZE as u8;
|
||||||
|
|
||||||
|
let ptr = unsafe {
|
||||||
|
cmd.as_mut_ptr().offset(6) as *mut c_void
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
libc::memcpy(ptr, data.offset(n as isize) as *const c_void, DATA_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Compute checksum
|
||||||
|
let mut checksum: u8 = cmd[1];
|
||||||
|
for idx in 2..(6+DATA_SIZE) {
|
||||||
|
checksum = checksum.overflowing_add(cmd[idx]).0;
|
||||||
|
}
|
||||||
|
cmd[DATA_SIZE+6] = checksum;
|
||||||
|
cmd[DATA_SIZE+7] = b'\x06'; // CMD_ACK byte
|
||||||
|
let reply = send_receive(&mut port, &cmd, 1, true);
|
||||||
|
if reply[0] != b'\x06' {
|
||||||
|
eprintln!("serial_write_region: wrong acknowledge {:?}, expected: {:?}", reply, b'\x06');
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
n += DATA_SIZE as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Query and return the device identification string.
|
||||||
|
/// On error, return None.
|
||||||
|
pub fn identify(dev_path: &str) -> Option<String> {
|
||||||
|
use serial::prelude::*;
|
||||||
|
|
||||||
|
let mut port = match serial::open(dev_path) {
|
||||||
|
Ok(port) => port,
|
||||||
|
Err(err) => {
|
||||||
|
println!("{}", err);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
port.reconfigure(&|settings: &mut dyn SerialPortSettings| {
|
||||||
|
settings.set_baud_rate(serial::BaudRate::Baud115200)?;
|
||||||
|
settings.set_char_size(serial::CharSize::Bits8);
|
||||||
|
Ok(())
|
||||||
|
}).unwrap();
|
||||||
|
port.set_timeout(Duration::new(1,0)).unwrap();
|
||||||
|
|
||||||
|
let program_cmd = b"PROGRAM";
|
||||||
|
let program_cmd_2 = b"\x02";
|
||||||
|
|
||||||
|
let program_ack = b"QX\x06";
|
||||||
|
let command_ack: u8 = b'\x06';
|
||||||
|
|
||||||
|
let output = send_receive(&mut port, program_cmd, 3, true);
|
||||||
|
if output != program_ack {
|
||||||
|
eprintln!("serial identify: wrong PRG acknowledge {:?}, expected {:?}", output, program_ack);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expected output should be something like:
|
||||||
|
// 49 44 38 36 38 55 56 45 00 56 31 30 32 00 00 06
|
||||||
|
// I D 8 6 8 U V E V 1 0 2
|
||||||
|
let output = send_receive(&mut port, program_cmd_2, 16, true);
|
||||||
|
if (output[0] != 'I' as u8) || (output[15] != command_ack) {
|
||||||
|
eprintln!("serial identify: wrong PRG2 reply {:?} expected 'I'...'\\6'", output);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(String::from_utf8(output[1..7].to_vec()).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close the serial port.
|
||||||
|
pub fn close() {
|
||||||
|
unsafe {
|
||||||
|
serial_close();
|
||||||
|
}
|
||||||
|
}
|
5
util.h
5
util.h
@ -93,11 +93,12 @@ void hid_write_finish(void);
|
|||||||
//
|
//
|
||||||
// Serial functions.
|
// Serial functions.
|
||||||
//
|
//
|
||||||
int serial_init(int vid, int pid);
|
//int serial_init(int vid, int pid);
|
||||||
const char *serial_identify(void);
|
//const char *serial_identify(void);
|
||||||
void serial_close(void);
|
void serial_close(void);
|
||||||
void serial_read_region(int addr, unsigned char *data, int nbytes);
|
void serial_read_region(int addr, unsigned char *data, int nbytes);
|
||||||
void serial_write_region(int addr, unsigned char *data, int nbytes);
|
void serial_write_region(int addr, unsigned char *data, int nbytes);
|
||||||
|
char *find_path(int vid, int pid);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Delay in milliseconds.
|
// Delay in milliseconds.
|
||||||
|
4
uv380.c
4
uv380.c
@ -400,6 +400,7 @@ static void uv380_print_version(radio_device_t *radio, FILE *out)
|
|||||||
static void uv380_download(radio_device_t *radio)
|
static void uv380_download(radio_device_t *radio)
|
||||||
{
|
{
|
||||||
int bno;
|
int bno;
|
||||||
|
int radio_progress = 0;
|
||||||
|
|
||||||
for (bno=0; bno<MEMSZ/1024; bno++) {
|
for (bno=0; bno<MEMSZ/1024; bno++) {
|
||||||
dfu_read_block(bno, &radio_mem[bno*1024], 1024);
|
dfu_read_block(bno, &radio_mem[bno*1024], 1024);
|
||||||
@ -418,6 +419,7 @@ static void uv380_download(radio_device_t *radio)
|
|||||||
static void uv380_upload(radio_device_t *radio, int cont_flag)
|
static void uv380_upload(radio_device_t *radio, int cont_flag)
|
||||||
{
|
{
|
||||||
int bno;
|
int bno;
|
||||||
|
int radio_progress = 0;
|
||||||
|
|
||||||
dfu_erase(0, MEMSZ);
|
dfu_erase(0, MEMSZ);
|
||||||
|
|
||||||
@ -2490,7 +2492,7 @@ static void uv380_write_csv(radio_device_t *radio, FILE *csv)
|
|||||||
// Erase whole region.
|
// Erase whole region.
|
||||||
// Align finish to 64kbytes.
|
// Align finish to 64kbytes.
|
||||||
//
|
//
|
||||||
radio_progress = 0;
|
int radio_progress = 0;
|
||||||
if (! trace_flag) {
|
if (! trace_flag) {
|
||||||
fprintf(stderr, "Erase: ");
|
fprintf(stderr, "Erase: ");
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
|
Loading…
Reference in New Issue
Block a user