2018-08-20 22:47:13 -07:00
|
|
|
/*
|
|
|
|
* 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"
|
|
|
|
|
2018-09-10 18:50:55 -07:00
|
|
|
static struct {
|
|
|
|
char *ident;
|
|
|
|
radio_device_t *device;
|
|
|
|
} radio_tab[] = {
|
|
|
|
{ "DR780", &radio_md380 }, // TYT MD-380, Retevis RT3, RT8
|
|
|
|
{ "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
|
|
|
|
{ "ZD3688", &radio_d900 }, // Zastone D900
|
|
|
|
{ "TP660", &radio_dp880 }, // Zastone DP880
|
|
|
|
{ "ZN><:", &radio_rt27d }, // Radtel RT-27D
|
|
|
|
{ 0, 0 }
|
|
|
|
};
|
|
|
|
|
2018-08-20 22:47:13 -07:00
|
|
|
unsigned char radio_mem [1024*1024]; // Radio memory contents, up to 1Mbyte
|
|
|
|
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");
|
|
|
|
|
2018-09-04 23:37:42 -07:00
|
|
|
// Restore the normal radio mode.
|
|
|
|
dfu_reboot();
|
2018-08-25 20:20:51 -07:00
|
|
|
dfu_close();
|
2018-08-20 22:47:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Print a generic information about the device.
|
|
|
|
//
|
|
|
|
void radio_print_version(FILE *out)
|
|
|
|
{
|
2018-08-29 17:44:46 -07:00
|
|
|
device->print_version(device, out);
|
2018-08-20 22:47:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Connect to the radio and identify the type of device.
|
|
|
|
//
|
|
|
|
void radio_connect()
|
|
|
|
{
|
2018-08-25 20:20:51 -07:00
|
|
|
// Only TYT MD family for now.
|
|
|
|
const char *ident = dfu_init(0x0483, 0xdf11);
|
2018-09-10 18:50:55 -07:00
|
|
|
int i;
|
2018-08-25 20:20:51 -07:00
|
|
|
|
2018-09-10 18:50:55 -07:00
|
|
|
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);
|
2018-08-25 20:20:51 -07:00
|
|
|
exit(-1);
|
|
|
|
}
|
2018-09-05 21:03:03 -07:00
|
|
|
fprintf(stderr, "Connect to %s.\n", device->name);
|
2018-08-20 22:47:13 -07:00
|
|
|
}
|
|
|
|
|
2018-09-10 18:50:55 -07:00
|
|
|
//
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-20 22:47:13 -07:00
|
|
|
//
|
|
|
|
// Read firmware image from the device.
|
|
|
|
//
|
|
|
|
void radio_download()
|
|
|
|
{
|
|
|
|
radio_progress = 0;
|
2018-08-31 11:56:21 -07:00
|
|
|
if (! trace_flag)
|
2018-08-20 22:47:13 -07:00
|
|
|
fprintf(stderr, "Read device: ");
|
|
|
|
|
2018-08-29 17:44:46 -07:00
|
|
|
device->download(device);
|
2018-08-20 22:47:13 -07:00
|
|
|
|
2018-08-31 11:56:21 -07:00
|
|
|
if (! trace_flag)
|
2018-08-20 22:47:13 -07:00
|
|
|
fprintf(stderr, " done.\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Write firmware image to the device.
|
|
|
|
//
|
|
|
|
void radio_upload(int cont_flag)
|
|
|
|
{
|
|
|
|
// Check for compatibility.
|
2018-08-29 17:44:46 -07:00
|
|
|
if (! device->is_compatible(device)) {
|
2018-08-20 22:47:13 -07:00
|
|
|
fprintf(stderr, "Incompatible image - cannot upload.\n");
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
radio_progress = 0;
|
2018-08-31 11:56:21 -07:00
|
|
|
if (! trace_flag) {
|
2018-08-20 22:47:13 -07:00
|
|
|
fprintf(stderr, "Write device: ");
|
2018-08-26 23:48:20 -07:00
|
|
|
fflush(stderr);
|
|
|
|
}
|
2018-08-29 17:44:46 -07:00
|
|
|
device->upload(device, cont_flag);
|
2018-08-20 22:47:13 -07:00
|
|
|
|
2018-08-31 11:56:21 -07:00
|
|
|
if (! trace_flag)
|
2018-08-20 22:47:13 -07:00
|
|
|
fprintf(stderr, " done.\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Read firmware image from the binary file.
|
|
|
|
//
|
2018-09-05 21:03:03 -07:00
|
|
|
void radio_read_image(const char *filename)
|
2018-08-20 22:47:13 -07:00
|
|
|
{
|
|
|
|
FILE *img;
|
|
|
|
struct stat st;
|
|
|
|
|
2018-08-31 13:15:35 -07:00
|
|
|
fprintf(stderr, "Read codeplug from file '%s'.\n", filename);
|
2018-08-20 22:47:13 -07:00
|
|
|
|
|
|
|
// Guess device type by file size.
|
|
|
|
if (stat(filename, &st) < 0) {
|
|
|
|
perror(filename);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
switch (st.st_size) {
|
|
|
|
case 851968:
|
2018-08-24 17:12:55 -07:00
|
|
|
case 852533:
|
2018-08-20 22:47:13 -07:00
|
|
|
device = &radio_uv380;
|
|
|
|
break;
|
2018-08-24 20:13:27 -07:00
|
|
|
case 262144:
|
|
|
|
case 262709:
|
|
|
|
device = &radio_md380;
|
|
|
|
break;
|
2018-08-20 22:47:13 -07:00
|
|
|
default:
|
|
|
|
fprintf(stderr, "%s: Unrecognized file size %u bytes.\n",
|
|
|
|
filename, (int) st.st_size);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
img = fopen(filename, "rb");
|
|
|
|
if (! img) {
|
|
|
|
perror(filename);
|
|
|
|
exit(-1);
|
|
|
|
}
|
2018-08-29 17:44:46 -07:00
|
|
|
device->read_image(device, img);
|
2018-08-20 22:47:13 -07:00
|
|
|
fclose(img);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Save firmware image to the binary file.
|
|
|
|
//
|
2018-09-05 21:03:03 -07:00
|
|
|
void radio_save_image(const char *filename)
|
2018-08-20 22:47:13 -07:00
|
|
|
{
|
|
|
|
FILE *img;
|
|
|
|
|
2018-08-31 13:15:35 -07:00
|
|
|
fprintf(stderr, "Write codeplug to file '%s'.\n", filename);
|
2018-09-07 23:38:52 -07:00
|
|
|
img = fopen(filename, "wb");
|
2018-08-20 22:47:13 -07:00
|
|
|
if (! img) {
|
|
|
|
perror(filename);
|
|
|
|
exit(-1);
|
|
|
|
}
|
2018-08-29 17:44:46 -07:00
|
|
|
device->save_image(device, img);
|
2018-08-20 22:47:13 -07:00
|
|
|
fclose(img);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Read the configuration from text file, and modify the firmware.
|
|
|
|
//
|
2018-09-05 21:03:03 -07:00
|
|
|
void radio_parse_config(const char *filename)
|
2018-08-20 22:47:13 -07:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2018-08-30 00:45:29 -07:00
|
|
|
device->channel_count = 0;
|
2018-08-20 22:47:13 -07:00
|
|
|
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.
|
2018-08-29 17:44:46 -07:00
|
|
|
table_id = device->parse_header(device, p);
|
2018-08-20 22:47:13 -07:00
|
|
|
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++;
|
|
|
|
|
2018-08-29 17:44:46 -07:00
|
|
|
device->parse_parameter(device, p, v);
|
2018-08-20 22:47:13 -07:00
|
|
|
|
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
|
2018-08-29 17:44:46 -07:00
|
|
|
if (! device->parse_row(device, table_id, ! table_dirty, p)) {
|
2018-08-20 22:47:13 -07:00
|
|
|
goto badline;
|
|
|
|
}
|
|
|
|
table_dirty = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fclose(conf);
|
2018-08-29 21:04:13 -07:00
|
|
|
device->update_timestamp(device);
|
2018-08-20 22:47:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// 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, "# This configuration was generated %sby dmrconfig,\n", buf);
|
|
|
|
fprintf(out, "# Version %s, %s\n", version, copyright);
|
|
|
|
fprintf(out, "#\n");
|
|
|
|
}
|
2018-08-29 17:44:46 -07:00
|
|
|
device->print_config(device, out, verbose);
|
2018-08-20 22:47:13 -07:00
|
|
|
}
|
2018-08-31 18:19:53 -07:00
|
|
|
|
|
|
|
//
|
|
|
|
// Check the configuration is correct.
|
|
|
|
//
|
|
|
|
void radio_verify_config()
|
|
|
|
{
|
|
|
|
if (!device->verify_config(device)) {
|
|
|
|
// Message should be already printed.
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
}
|
2018-09-05 21:03:03 -07:00
|
|
|
|
|
|
|
//
|
|
|
|
// 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);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
2018-09-07 23:38:52 -07:00
|
|
|
csv = fopen(filename, "r");
|
2018-09-05 21:03:03 -07:00
|
|
|
if (! csv) {
|
|
|
|
perror(filename);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
fprintf(stderr, "Read file '%s'.\n", filename);
|
|
|
|
|
|
|
|
device->write_csv(device, csv);
|
|
|
|
fclose(csv);
|
|
|
|
}
|