diff --git a/Makefile b/Makefile index e6e2706..a99c450 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ UNAME = $(shell uname) CFLAGS = -g -O -Wall -Werror -DVERSION='"$(VERSION).$(GITCOUNT)"' LDFLAGS = -g -OBJS = main.o util.o radio.o dfu-libusb.o uv380.o md380.o rd5r.o gd77.o hid.o serial.o +OBJS = main.o util.o radio.o dfu-libusb.o uv380.o md380.o rd5r.o gd77.o hid.o serial.o d868uv.o LIBS = -lusb-1.0 # Linux @@ -37,6 +37,7 @@ dmrconfig.linux: dmrconfig strip $@ ### +d868uv.o: d868uv.c radio.h util.h dfu-libusb.o: dfu-libusb.c util.h dfu-windows.o: dfu-windows.c util.h gd77.o: gd77.c radio.h util.h @@ -48,5 +49,6 @@ main.o: main.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 +serial.o: serial.c util.h util.o: util.c util.h uv380.o: uv380.c radio.h util.h diff --git a/Makefile-mingw b/Makefile-mingw index 9cbbe55..242a941 100644 --- a/Makefile-mingw +++ b/Makefile-mingw @@ -5,7 +5,7 @@ GITCOUNT = $(shell git rev-list HEAD --count) CFLAGS = -g -O -Wall -Werror -DVERSION='"$(VERSION).$(GITCOUNT)"' LDFLAGS = -g -s -OBJS = main.o util.o radio.o dfu-windows.o uv380.o md380.o rd5r.o gd77.o hid.o hid-windows.o serial.o +OBJS = main.o util.o radio.o dfu-windows.o uv380.o md380.o rd5r.o gd77.o hid.o hid-windows.o serial.o d868uv.o LIBS = -lhid -lsetupapi # Compiling Windows binary from Linux @@ -33,6 +33,7 @@ install: dmrconfig install -c -s dmrconfig /usr/local/bin/dmrconfig ### +d868uv.o: d868uv.c radio.h util.h dfu-libusb.o: dfu-libusb.c util.h dfu-windows.o: dfu-windows.c util.h gd77.o: gd77.c radio.h util.h @@ -44,5 +45,6 @@ main.o: main.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 +serial.o: serial.c util.h util.o: util.c util.h uv380.o: uv380.c radio.h util.h diff --git a/d868uv.c b/d868uv.c new file mode 100644 index 0000000..0a5c937 --- /dev/null +++ b/d868uv.c @@ -0,0 +1,466 @@ +/* + * Interface to Anytone D868UV. + * + * 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 +#include +#include +#include +#include +#include +#include "radio.h" +#include "util.h" + +#define NCHAN 4000 +#define NCONTACTS 10000 +#define NZONES 250 +#define NGLISTS 250 +#define NSCANL 250 +#define NMESSAGES 100 + +#define MEMSZ 0xd0000 + +// +// Print a generic information about the device. +// +static void d868uv_print_version(radio_device_t *radio, FILE *out) +{ + //TODO +#if 0 + unsigned char *timestamp = GET_TIMESTAMP(); + static const char charmap[16] = "0123456789:;<=>?"; + + if (*timestamp != 0xff) { + fprintf(out, "Last Programmed Date: %d%d%d%d-%d%d-%d%d", + timestamp[0] >> 4, timestamp[0] & 15, timestamp[1] >> 4, timestamp[1] & 15, + timestamp[2] >> 4, timestamp[2] & 15, timestamp[3] >> 4, timestamp[3] & 15); + fprintf(out, " %d%d:%d%d:%d%d\n", + timestamp[4] >> 4, timestamp[4] & 15, timestamp[5] >> 4, timestamp[5] & 15, + timestamp[6] >> 4, timestamp[6] & 15); + fprintf(out, "CPS Software Version: V%c%c.%c%c\n", + charmap[timestamp[7] & 15], charmap[timestamp[8] & 15], + charmap[timestamp[9] & 15], charmap[timestamp[10] & 15]); + } +#endif +} + +// +// Read memory image from the device. +// +static void d868uv_download(radio_device_t *radio) +{ + //TODO +#if 0 + int bno; + + for (bno=0; bnoscan_list_index != 0) { + scanlist_t *sl = GET_SCANLIST(ch->scan_list_index - 1); + + if (!VALID_SCANLIST(sl)) { + fprintf(stderr, "Channel %d '", i+1); + print_unicode(stderr, ch->name, 16, 0); + fprintf(stderr, "': scanlist %d not found.\n", ch->scan_list_index); + nerrors++; + } + } + if (ch->contact_name_index != 0) { + contact_t *ct = GET_CONTACT(ch->contact_name_index - 1); + + if (!VALID_CONTACT(ct)) { + fprintf(stderr, "Channel %d '", i+1); + print_unicode(stderr, ch->name, 16, 0); + fprintf(stderr, "': contact %d not found.\n", ch->contact_name_index); + nerrors++; + } + } + if (ch->group_list_index != 0) { + grouplist_t *gl = GET_GROUPLIST(ch->group_list_index - 1); + + if (!VALID_GROUPLIST(gl)) { + fprintf(stderr, "Channel %d '", i+1); + print_unicode(stderr, ch->name, 16, 0); + fprintf(stderr, "': grouplist %d not found.\n", ch->group_list_index); + nerrors++; + } + } + } + + // Zones: check references to channels. + for (i=0; imember_a[k]; + + if (cnum != 0) { + channel_t *ch = GET_CHANNEL(cnum - 1); + + if (!VALID_CHANNEL(ch)) { + fprintf(stderr, "Zone %da '", i+1); + print_unicode(stderr, z->name, 16, 0); + fprintf(stderr, "': channel %d not found.\n", cnum); + nerrors++; + } + } + } + for (k=0; k<48; k++) { + int cnum = zext->ext_a[k]; + + if (cnum != 0) { + channel_t *ch = GET_CHANNEL(cnum - 1); + + if (!VALID_CHANNEL(ch)) { + fprintf(stderr, "Zone %da '", i+1); + print_unicode(stderr, z->name, 16, 0); + fprintf(stderr, "': channel %d not found.\n", cnum); + nerrors++; + } + } + } + + // Zone B + for (k=0; k<64; k++) { + int cnum = zext->member_b[k]; + + if (cnum != 0) { + channel_t *ch = GET_CHANNEL(cnum - 1); + + if (!VALID_CHANNEL(ch)) { + fprintf(stderr, "Zone %db '", i+1); + print_unicode(stderr, z->name, 16, 0); + fprintf(stderr, "': channel %d not found.\n", cnum); + nerrors++; + } + } + } + } + + // Scanlists: check references to channels. + for (i=0; imember[k]; + + if (cnum != 0) { + channel_t *ch = GET_CHANNEL(cnum - 1); + + if (!VALID_CHANNEL(ch)) { + fprintf(stderr, "Scanlist %d '", i+1); + print_unicode(stderr, sl->name, 16, 0); + fprintf(stderr, "': channel %d not found.\n", cnum); + nerrors++; + } + } + } + } + + // Grouplists: check references to contacts. + for (i=0; imember[k]; + + if (cnum != 0) { + contact_t *ct = GET_CONTACT(cnum - 1); + + if (!VALID_CONTACT(ct)) { + fprintf(stderr, "Grouplist %d '", i+1); + print_unicode(stderr, gl->name, 16, 0); + fprintf(stderr, "': contact %d not found.\n", cnum); + nerrors++; + } + } + } + } + + // Count contacts. + for (i=0; i 0) { + fprintf(stderr, "Total %d errors.\n", nerrors); + return 0; + } + fprintf(stderr, "Total %d channels, %d zones, %d scanlists, %d contacts, %d grouplists.\n", + nchannels, nzones, nscanlists, ncontacts, ngrouplists); +#endif + return 1; +} + +// +// TYT MD-UV380 +// +radio_device_t radio_d868uv = { + "Anytone AT-D868UV", + d868uv_download, + d868uv_upload, + d868uv_is_compatible, + d868uv_read_image, + d868uv_save_image, + d868uv_print_version, + d868uv_print_config, + d868uv_verify_config, + d868uv_parse_parameter, + d868uv_parse_header, + d868uv_parse_row, + d868uv_update_timestamp, + //d868uv_write_csv, +}; diff --git a/radio.c b/radio.c index 0c860e5..0d5592f 100644 --- a/radio.c +++ b/radio.c @@ -49,6 +49,7 @@ static struct { { "ZN><:", &radio_rt27d }, // Radtel RT-27D { "BF-5R", &radio_rd5r }, // Baofeng RD-5R { "MD-760P", &radio_gd77 }, // Radioddity GD-77, version 3.1.1 and later + { "D868UVE", &radio_d868uv }, // Anytone AT-D868UV { 0, 0 } }; diff --git a/radio.h b/radio.h index b7dabe7..99f8582 100644 --- a/radio.h +++ b/radio.h @@ -124,6 +124,7 @@ extern radio_device_t radio_dp880; // Zastone DP880 extern radio_device_t radio_rt27d; // Radtel RT-27D extern radio_device_t radio_rd5r; // Baofeng RD-5R extern radio_device_t radio_gd77; // Radioddity GD-77, version 3.1.1 and later +extern radio_device_t radio_d868uv; // Anytone AT-D868UV // // Radio: memory contents. diff --git a/serial.c b/serial.c index ba5f2d8..756708b 100644 --- a/serial.c +++ b/serial.c @@ -59,8 +59,10 @@ static char *dev_path; static const unsigned char CMD_PRG[] = "PROGRAM"; +static const unsigned char CMD_PRG2[] = "\2"; static const unsigned char CMD_QX[] = "QX\6"; -static const unsigned char CMD_ACK2[] = "\2"; +static const unsigned char CMD_ACK[] = "\6"; +static const unsigned char CMD_END[] = "END"; // // Encode the speed in bits per second into bit value @@ -250,26 +252,6 @@ again: return got; } -// -// Close the serial port. -// -void serial_close() -{ -#if defined(__WIN32__) || defined(WIN32) - if (fd != INVALID_HANDLE_VALUE) { - SetCommState(fd, &saved_mode); - CloseHandle(fd); - fd = INVALID_HANDLE_VALUE; - } -#else - if (fd >= 0) { - tcsetattr(fd, TCSANOW, &saved_mode); - close(fd); - fd = -1; - } -#endif -} - // // Open the serial port. // Return -1 on error. @@ -601,8 +583,33 @@ static int send_recv(const unsigned char *cmd, int cmdlen, return 1; } +// +// Close the serial port. +// +void serial_close() +{ +#if defined(__WIN32__) || defined(WIN32) + if (fd != INVALID_HANDLE_VALUE) { + SetCommState(fd, &saved_mode); + CloseHandle(fd); + fd = INVALID_HANDLE_VALUE; + } +#else + if (fd >= 0) { + unsigned char ack[1]; + + send_recv(CMD_END, 3, ack, 1); + + tcsetattr(fd, TCSANOW, &saved_mode); + close(fd); + fd = -1; + } +#endif +} + // // Query and return the device identification string. +// On error, return NULL. // const char *serial_identify() { @@ -614,7 +621,7 @@ const char *serial_identify() } send_recv(CMD_PRG, 7, ack, 3); - if (ack[0] != CMD_QX[0] || ack[1] != CMD_QX[1] || ack[2] != CMD_QX[2]) { + if (memcmp(ack, CMD_QX, 3) != 0) { fprintf(stderr, "%s: Wrong PRG acknowledge %02x-%02x-%02x, expected %02x-%02x-%02x\n", __func__, ack[0], ack[1], ack[2], CMD_QX[0], CMD_QX[1], CMD_QX[2]); return 0; @@ -623,9 +630,14 @@ const char *serial_identify() // Reply: // 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 - send_recv(CMD_ACK2, 1, reply, 16); + send_recv(CMD_PRG2, 1, reply, 16); + if (reply[0] != 'I' || reply[15] != CMD_ACK[0]) { + fprintf(stderr, "%s: Wrong PRG2 reply %02x-...-%02x, expected %02x-...-%02x\n", + __func__, reply[0], reply[15], 'I', CMD_ACK[0]); + return 0; + } // Terminate the string. reply[8] = 0; - return (char*)reply; + return (char*)&reply[1]; }