/* * 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 // // Size of memory image. // Essentialy a sum of all fragments defined ind868um-map.h. // #define MEMSZ 1607296 // // D868UV radio has a huge internal address space, more than 64 Mbytes. // The configuration data are dispersed over this space. // Here is a table of fragments: starting address and length. // We read these fragments and save them into a file continuously. // typedef struct { unsigned address; unsigned length; } fragment_t; static fragment_t region_map[] = { #include "d868uv-map.h" }; // // 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) { fragment_t *f; unsigned file_offset = 0; unsigned last_printed = 0; for (f=region_map; f->length; f++) { unsigned addr = f->address; unsigned nbytes = f->length; while (nbytes > 0) { unsigned n = (nbytes > 32*1024) ? 32*1024 : nbytes; serial_read_region(addr, &radio_mem[file_offset], n); file_offset += n; addr += n; nbytes -= n; if (file_offset / (32*1024) != last_printed) { fprintf(stderr, "#"); fflush(stderr); last_printed = file_offset / (32*1024); } } } if (file_offset != MEMSZ) { fprintf(stderr, "\nWrong MEMSZ=%u for D868UV!\n", MEMSZ); fprintf(stderr, "Should be %u; check d868uv-map.h!\n", file_offset); exit(-1); } } // // Write memory image to the device. // static void d868uv_upload(radio_device_t *radio, int cont_flag) { fragment_t *f; unsigned file_offset = 0; unsigned last_printed = 0; for (f=region_map; f->length; f++) { unsigned addr = f->address; unsigned nbytes = f->length; while (nbytes > 0) { unsigned n = (nbytes > 32*1024) ? 32*1024 : nbytes; serial_write_region(addr, &radio_mem[file_offset], n); file_offset += n; addr += n; nbytes -= n; if (file_offset / (32*1024) != last_printed) { fprintf(stderr, "#"); fflush(stderr); last_printed = file_offset / (32*1024); } } } if (file_offset != MEMSZ) { fprintf(stderr, "\nWrong MEMSZ=%u for D868UV!\n", MEMSZ); fprintf(stderr, "Should be %u; check d868uv-map.h!\n", file_offset); exit(-1); } } // // Check whether the memory image is compatible with this device. // static int d868uv_is_compatible(radio_device_t *radio) { return 1; } // // Print full information about the device configuration. // static void d868uv_print_config(radio_device_t *radio, FILE *out, int verbose) { //TODO } // // Read memory image from the binary file. // static void d868uv_read_image(radio_device_t *radio, FILE *img) { //TODO #if 0 struct stat st; // Guess device type by file size. if (fstat(fileno(img), &st) < 0) { fprintf(stderr, "Cannot get file size.\n"); exit(-1); } switch (st.st_size) { case MEMSZ: // IMG file. if (fread(&radio_mem[0], 1, MEMSZ, img) != MEMSZ) { fprintf(stderr, "Error reading image data.\n"); exit(-1); } break; case MEMSZ + 0x225 + 0x10: // RTD file. // Header 0x225 bytes and footer 0x10 bytes at 0x40225. fseek(img, 0x225, SEEK_SET); if (fread(&radio_mem[0], 1, 0x40000, img) != 0x40000) { fprintf(stderr, "Error reading image data.\n"); exit(-1); } fseek(img, 0x10, SEEK_CUR); if (fread(&radio_mem[0x40000], 1, MEMSZ - 0x40000, img) != MEMSZ - 0x40000) { fprintf(stderr, "Error reading image data.\n"); exit(-1); } break; default: fprintf(stderr, "Unrecognized file size %u bytes.\n", (int) st.st_size); exit(-1); } #endif } // // Save memory image to the binary file. // static void d868uv_save_image(radio_device_t *radio, FILE *img) { fwrite(&radio_mem[0], 1, MEMSZ, img); } // // Parse the scalar parameter. // static void d868uv_parse_parameter(radio_device_t *radio, char *param, char *value) { //TODO fprintf(stderr, "Unknown parameter: %s = %s\n", param, value); exit(-1); } // // Parse table header. // Return table id, or 0 in case of error. // static int d868uv_parse_header(radio_device_t *radio, char *line) { if (strncasecmp(line, "Digital", 7) == 0) return 'D'; if (strncasecmp(line, "Analog", 6) == 0) return 'A'; if (strncasecmp(line, "Zone", 4) == 0) return 'Z'; if (strncasecmp(line, "Scanlist", 8) == 0) return 'S'; if (strncasecmp(line, "Contact", 7) == 0) return 'C'; if (strncasecmp(line, "Grouplist", 9) == 0) return 'G'; if (strncasecmp(line, "Message", 7) == 0) return 'M'; return 0; } // // Parse one line of table data. // Return 0 on failure. // static int d868uv_parse_row(radio_device_t *radio, int table_id, int first_row, char *line) { //TODO #if 0 switch (table_id) { case 'D': return parse_digital_channel(radio, first_row, line); case 'A': return parse_analog_channel(radio, first_row, line); case 'Z': return parse_zones(first_row, line); case 'S': return parse_scanlist(first_row, line); case 'C': return parse_contact(first_row, line); case 'G': return parse_grouplist(first_row, line); case 'M': return parse_messages(first_row, line); } #endif return 0; } // // Update timestamp. // static void d868uv_update_timestamp(radio_device_t *radio) { //TODO #if 0 unsigned char *timestamp = GET_TIMESTAMP(); char p[16]; // Last Programmed Date get_timestamp(p); timestamp[0] = ((p[0] & 0xf) << 4) | (p[1] & 0xf); // year upper timestamp[1] = ((p[2] & 0xf) << 4) | (p[3] & 0xf); // year lower timestamp[2] = ((p[4] & 0xf) << 4) | (p[5] & 0xf); // month timestamp[3] = ((p[6] & 0xf) << 4) | (p[7] & 0xf); // day timestamp[4] = ((p[8] & 0xf) << 4) | (p[9] & 0xf); // hour timestamp[5] = ((p[10] & 0xf) << 4) | (p[11] & 0xf); // minute timestamp[6] = ((p[12] & 0xf) << 4) | (p[13] & 0xf); // second // CPS Software Version: Vdx.xx const char *dot = strchr(VERSION, '.'); if (dot) { timestamp[7] = 0x0d; // Prints as '=' timestamp[8] = dot[-1] & 0x0f; if (dot[2] == '.') { timestamp[9] = 0; timestamp[10] = dot[1] & 0x0f; } else { timestamp[9] = dot[1] & 0x0f; timestamp[10] = dot[2] & 0x0f; } } #endif } // // Check that configuration is correct. // Return 0 on error. // static int d868uv_verify_config(radio_device_t *radio) { //TODO #if 0 int i, k, nchannels = 0, nzones = 0, nscanlists = 0, ngrouplists = 0; int ncontacts = 0, nerrors = 0; // Channels: check references to scanlists, contacts and grouplists. for (i=0; iscan_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, };