/* * Interface to Radioddity GD-77, firmware version 3.1.1 and later. * * 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 1024 #define NCONTACTS 1024 #define NZONES 250 #define NGLISTS 76 #define NSCANL 64 #define NMESSAGES 32 #define MEMSZ 0x20000 #define OFFSET_TIMESTMP 0x00088 #define OFFSET_SETTINGS 0x000e0 #define OFFSET_MSGTAB 0x00128 #define OFFSET_SCANTAB 0x01790 #define OFFSET_BANK_0 0x03780 // Channels 1-128 #define OFFSET_INTRO 0x07540 #define OFFSET_ZONETAB 0x08010 #define OFFSET_BANK_1 0x0b1b0 // Channels 129-1024 #define OFFSET_CONTACTS 0x17620 #define OFFSET_GROUPTAB 0x1d620 #define GET_TIMESTAMP() (&radio_mem[OFFSET_TIMESTMP]) #define GET_SETTINGS() ((general_settings_t*) &radio_mem[OFFSET_SETTINGS]) #define GET_INTRO() ((intro_text_t*) &radio_mem[OFFSET_INTRO]) #define GET_ZONETAB() ((zonetab_t*) &radio_mem[OFFSET_ZONETAB]) #define GET_SCANTAB(i) ((scantab_t*) &radio_mem[OFFSET_SCANTAB]) #define GET_GROUPTAB() ((grouptab_t*) &radio_mem[OFFSET_GROUPTAB]) #define GET_MSGTAB() ((msgtab_t*) &radio_mem[OFFSET_MSGTAB]) #define GET_CONTACT(i) ((contact_t*) &radio_mem[OFFSET_CONTACTS + (i)*24]) #define VALID_TEXT(txt) (*(txt) != 0 && *(txt) != 0xff) #define VALID_CONTACT(ct) ((ct) != 0 && VALID_TEXT((ct)->name)) // // Channel data. // typedef struct { // Bytes 0-15 uint8_t name[16]; // Channel Name // Bytes 16-23 uint32_t rx_frequency; // RX Frequency: 8 digits BCD uint32_t tx_frequency; // TX Frequency: 8 digits BCD // Byte 24 uint8_t channel_mode; // Mode: Analog or Digital #define MODE_ANALOG 0 #define MODE_DIGITAL 1 // Bytes 25-26 uint8_t _unused25[2]; // 0 // Bytes 27-28 uint8_t tot; // TOT x 15sec: 0-Infinite, 1=15s... 33=495s uint8_t tot_rekey_delay; // TOT Rekey Delay: 0s...255s // Byte 29 uint8_t admit_criteria; // Admit Criteria: Always, Channel Free or Color Code #define ADMIT_ALWAYS 0 #define ADMIT_CH_FREE 1 #define ADMIT_COLOR 2 // Bytes 30-31 uint8_t _unused30; // 0x50 uint8_t scan_list_index; // Scan List: None, ScanList1...250 // Bytes 32-35 uint16_t ctcss_dcs_receive; // CTCSS/DCS Dec: 4 digits BCD or 0xffff uint16_t ctcss_dcs_transmit; // CTCSS/DCS Enc: 4 digits BCD // Bytes 36-39 uint8_t _unused36; // 0 uint8_t tx_signaling_syst; // Tx Signaling System: Off, DTMF uint8_t _unused38; // 0 uint8_t rx_signaling_syst; // Rx Signaling System: Off, DTMF // Bytes 40-43 uint8_t _unused40; // 0x16 uint8_t privacy_group; // Privacy Group: 0=None, 1=53474c39 #define PRIVGR_NONE 0 #define PRIVGR_53474C39 1 uint8_t colorcode_tx; // Color Code: 0...15 uint8_t group_list_index; // Group List: None, GroupList1...128 // Bytes 44-47 uint8_t colorcode_rx; // Color Code: 0...15 uint8_t emergency_system_index; // Emergency System: None, System1...32 uint16_t contact_name_index; // Contact Name: Contact1... // Byte 48 uint8_t _unused48 : 6, // 0 emergency_alarm_ack : 1, // Emergency Alarm Ack data_call_conf : 1; // Data Call Confirmed // Byte 49 uint8_t private_call_conf : 1, // Private Call Confirmed _unused49_1 : 3, // 0 privacy : 1, // Privacy: Off or On _unused49_5 : 1, // 0 repeater_slot2 : 1, // Repeater Slot: 0=slot1 or 1=slot2 _unused49_7 : 1; // 0 // Byte 50 uint8_t dcdm : 1, // Dual Capacity Direct Mode _unused50_1 : 4, // 0 non_ste_frequency : 1, // Non STE = Frequency _unused50_6 : 2; // 0 // Byte 51 uint8_t squelch : 1, // Squelch #define SQ_TIGHT 0 #define SQ_NORMAL 1 bandwidth : 1, // Bandwidth: 12.5 or 25 kHz #define BW_12_5_KHZ 0 #define BW_25_KHZ 1 rx_only : 1, // RX Only Enable talkaround : 1, // Allow Talkaround _unused51_4 : 2, // 0 vox : 1, // VOX Enable power : 1; // Power: Low, High #define POWER_HIGH 1 #define POWER_LOW 0 // Bytes 52-55 uint8_t _unused52[4]; // 0 } channel_t; // // Bank of 128 channels. // typedef struct { uint8_t bitmap[16]; // bit set when channel valid channel_t chan[128]; } bank_t; // // Contact data. // typedef struct { // Bytes 0-15 uint8_t name[16]; // Contact Name, ff terminated // Bytes 16-19 uint8_t id[4]; // BCD coded 8 digits #define GET_ID(x) (((x)[0] >> 4) * 10000000 +\ ((x)[0] & 15) * 1000000 +\ ((x)[1] >> 4) * 100000 +\ ((x)[1] & 15) * 10000 +\ ((x)[2] >> 4) * 1000 +\ ((x)[2] & 15) * 100 +\ ((x)[3] >> 4) * 10 +\ ((x)[3] & 15)) #define CONTACT_ID(ct) GET_ID((ct)->id) // Byte 20 uint8_t type; // Call Type: Group Call, Private Call or All Call #define CALL_GROUP 0 #define CALL_PRIVATE 1 #define CALL_ALL 2 // Bytes 21-23 uint8_t receive_tone; // Call Receive Tone: 0=Off, 1=On uint8_t ring_style; // Ring style: 0-10 uint8_t _unused23; // 0xff for used contact, 0 for blank entry } contact_t; // // Zone data. // typedef struct { // Bytes 0-15 uint8_t name[16]; // Zone Name // Bytes 16-47 uint16_t member[16]; // Member: channels 1...16 } zone_t; // // Table of zones. // typedef struct { uint8_t bitmap[32]; // bit set when zone valid zone_t zone[NZONES]; } zonetab_t; // // Group list data. // typedef struct { // Bytes 0-15 uint8_t name[16]; // Group List Name // Bytes 16-79 uint16_t member[32]; // Contacts } grouplist_t; // // Table of group lists. // typedef struct { uint8_t nitems1[128]; // N+1, zero when disabled grouplist_t grouplist[NGLISTS]; } grouptab_t; // // Scan list data. // typedef struct { // Bytes 0-14 uint8_t name[15]; // Scan List Name // Byte 15 uint8_t _unused : 4, // 0 channel_mark : 1, // Channel Mark, default 1 pl_type : 2, // PL Type, default 3 #define PL_NONPRI 0 // Non-Priority Channel #define PL_DISABLE 1 // Disable #define PL_PRI 2 // Priority Channel #define PL_PRI_NONPRI 3 // Priority and Non-Priority Channels talkback : 1; // Talkback, default 1 // Bytes 16-79 uint16_t member[32]; // Channels (+1) #define CHAN_SELECTED 1 // Selected // Bytes 80-85 uint16_t priority_ch1; // Priority Channel 1, chan+1 or 0=None, 1=Selected uint16_t priority_ch2; // Priority Channel 2 uint16_t tx_designated_ch; // Tx Designated Channel, chan+1 or 0=Last Active Channel // Bytes 86-87 uint8_t sign_hold_time; // Signaling Hold Time (x25 = msec) default 40=1000ms uint8_t prio_sample_time; // Priority Sample Time (x250 = msec) default 8=2000ms } scanlist_t; // // Table of scanlists. // typedef struct { uint8_t valid[NSCANL]; // byte=1 when scanlist valid scanlist_t scanlist[NSCANL]; } scantab_t; // // General settings. // typedef struct { // Bytes e0-e7 uint8_t radio_name[8]; // Bytes e8-eb uint8_t radio_id[4]; } general_settings_t; // // Intro messages. // typedef struct { // Bytes 7540-754f uint8_t intro_line1[16]; // Bytes 7550-755f uint8_t intro_line2[16]; } intro_text_t; // // Table of text messages. // typedef struct { uint8_t count; // number of messages uint8_t _unused1[7]; // 0 uint8_t len[NMESSAGES]; // message length uint8_t _unused2[NMESSAGES]; // 0 uint8_t message[NMESSAGES*144]; // messages } msgtab_t; static const char *POWER_NAME[] = { "Low", "High" }; static const char *SQUELCH_NAME[] = { "Tight", "Normal" }; static const char *BANDWIDTH[] = { "12.5", "25" }; static const char *CONTACT_TYPE[] = {"Group", "Private", "All", "???" }; static const char *ADMIT_NAME[] = { "-", "Free", "Color", "???" }; #ifdef PRINT_RARE_PARAMS static const char *PRIVACY_NAME[] = { "-", "On" }; static const char *SIGNALING_SYSTEM[] = { "-", "DTMF" }; #endif // // Print a generic information about the device. // static void gd77_print_version(radio_device_t *radio, FILE *out) { unsigned char *timestamp = GET_TIMESTAMP(); 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\n", timestamp[4] >> 4, timestamp[4] & 15, timestamp[5] >> 4, timestamp[5] & 15); } } // // Read memory image from the device. // static void download(radio_device_t *radio) { int bno; // Read range 0x80...0x1e29f. for (bno=1; bno<966; bno++) { if (bno >= 248 && bno < 256) { // Skip range 0x7c00...0x8000. continue; } hid_read_block(bno, &radio_mem[bno*128], 128); ++radio_progress; if (radio_progress % 32 == 0) { fprintf(stderr, "#"); fflush(stderr); } } //hid_read_finish(); // Clear header and footer. memset(&radio_mem[0], 0xff, 128); memset(&radio_mem[966*128], 0xff, MEMSZ - 966*128); memset(&radio_mem[248*128], 0xff, 8*128); } // // Radioddity GD-77 new firmware: read memory image. // static void gd77_download(radio_device_t *radio) { download(radio); // Add header. memcpy(&radio_mem[0], "MD-760P", 7); } // // Write memory image to the device. // static void gd77_upload(radio_device_t *radio, int cont_flag) { int bno; // Write range 0x80...0x1e29f. for (bno=1; bno<966; bno++) { if (bno >= 248 && bno < 256) { // Skip range 0x7c00...0x8000. continue; } hid_write_block(bno, &radio_mem[bno*128], 128); ++radio_progress; if (radio_progress % 32 == 0) { fprintf(stderr, "#"); fflush(stderr); } } hid_write_finish(); } // // Check whether the memory image is compatible with this device. // static int gd77_is_compatible(radio_device_t *radio) { return strncmp("MD-760P", (char*)&radio_mem[0], 7) == 0; } // // Set name for a given zone. // static void setup_zone(int index, const char *name) { zonetab_t *zt = GET_ZONETAB(); zone_t *z = &zt->zone[index]; ascii_decode(z->name, name, sizeof(z->name), 0xff); memset(z->member, 0, sizeof(z->member)); // Set valid bit. zt->bitmap[index / 8] |= 1 << (index & 7); } // // Get zone by index. // Return 0 when zone is disabled. // static zone_t *get_zone(int index) { zonetab_t *zt = GET_ZONETAB(); if (zt->bitmap[index / 8] >> (index & 7) & 1) return &zt->zone[index]; else return 0; } // // Add channel to a zone. // Return 0 on failure. // static int zone_append(int index, int cnum) { zonetab_t *zt = GET_ZONETAB(); zone_t *z = &zt->zone[index]; int i; for (i=0; i<16; i++) { if (z->member[i] == cnum) return 1; if (z->member[i] == 0) { z->member[i] = cnum; return 1; } } return 0; } static void erase_zone(int index) { zonetab_t *zt = GET_ZONETAB(); zone_t *z = &zt->zone[index]; memset(z->name, 0, sizeof(z->name)); memset(z->member, 0, sizeof(z->member)); // Clear valid bit. zt->bitmap[index / 8] &= ~(1 << (index & 7)); } // // Set parameters for a given scan list. // static void setup_scanlist(int index, const char *name, int prio1, int prio2, int txchan) { scantab_t *st = GET_SCANTAB(); scanlist_t *sl = &st->scanlist[index]; memset(sl, 0, 88); ascii_decode(sl->name, name, sizeof(sl->name), 0xff); sl->priority_ch1 = prio1; sl->priority_ch2 = prio2; sl->tx_designated_ch = txchan; sl->talkback = 1; sl->channel_mark = 1; sl->pl_type = PL_PRI_NONPRI; sl->sign_hold_time = 1000 / 25; // 1 sec sl->prio_sample_time = 2000 / 250; // 2 sec // Set valid bit. st->valid[index] = 1; } static void erase_scanlist(int index) { scantab_t *st = GET_SCANTAB(); memset(&st->scanlist[index], 0xff, sizeof(scanlist_t)); // Clear valid bit. st->valid[index] = 0; } // // Get scanlist by index. // Return 0 when scanlist is disabled. // static scanlist_t *get_scanlist(int index) { scantab_t *st = GET_SCANTAB(); if (st->valid[index]) return &st->scanlist[index]; else return 0; } // // Add channel to a zone. // Return 0 on failure. // static int scanlist_append(int index, int cnum) { scanlist_t *sl = get_scanlist(index); int i; if (!sl) return 0; // First element is always Selected. if (sl->member[0] == 0) sl->member[0] = CHAN_SELECTED; for (i=0; i<32; i++) { if (sl->member[i] == cnum + 1) return 1; if (sl->member[i] == 0) { sl->member[i] = cnum + 1; return 1; } } return 0; } static void erase_contact(int index) { contact_t *ct = GET_CONTACT(index); memset(ct->name, 0xff, sizeof(ct->name)); memset(ct->id, 0, 8); } static void setup_contact(int index, const char *name, int type, int id, int rxtone) { contact_t *ct = GET_CONTACT(index); ct->id[0] = ((id / 10000000) << 4) | ((id / 1000000) % 10); ct->id[1] = ((id / 100000 % 10) << 4) | ((id / 10000) % 10); ct->id[2] = ((id / 1000 % 10) << 4) | ((id / 100) % 10); ct->id[3] = ((id / 10 % 10) << 4) | (id % 10); ct->type = type; ct->receive_tone = rxtone; ct->ring_style = 0; // TODO ct->_unused23 = (type < CALL_ALL) ? 0 : 0xff; ascii_decode(ct->name, name, 16, 0xff); } // // Get grouplist by index. // Return 0 when grouplist is disabled. // static grouplist_t *get_grouplist(int index) { grouptab_t *gt = GET_GROUPTAB(); if (gt->nitems1[index] > 0) return >->grouplist[index]; else return 0; } static void setup_grouplist(int index, const char *name) { grouptab_t *gt = GET_GROUPTAB(); grouplist_t *gl = >->grouplist[index]; ascii_decode(gl->name, name, sizeof(gl->name), 0xff); // Enable grouplist. gt->nitems1[index] = 1; } // // Add contact to a grouplist. // Return 0 on failure. // static int grouplist_append(int index, int cnum) { grouptab_t *gt = GET_GROUPTAB(); grouplist_t *gl = >->grouplist[index]; int i; for (i=0; i<32; i++) { if (gl->member[i] == cnum) return 1; if (gl->member[i] == 0) { gl->member[i] = cnum; gt->nitems1[index] = i + 2; return 1; } } return 0; } // // Set text for a given message. // static void setup_message(int index, const char *text) { msgtab_t *mt = GET_MSGTAB(); uint8_t *msg = &mt->message[index*144]; int len, i; // Skip spaces and tabs. while (*text == ' ' || *text == '\t') text++; len = strlen(text); mt->len[index] = len + 1; memset(msg, 0xff, 144); memcpy(msg, text, len < 144 ? len : 144); // Count messages. mt->count = 0; for (i=0; ilen); i++) if (mt->len[i] > 0) mt->count++; } // // Check that the radio does support this frequency. // static int is_valid_frequency(int mhz) { if (mhz >= 136 && mhz <= 174) return 1; if (mhz >= 400 && mhz <= 480) return 1; return 0; } // // Get channel bank by index. // static bank_t *get_bank(int i) { if (i == 0) return (bank_t*) &radio_mem[OFFSET_BANK_0]; else return (i - 1) + (bank_t*) &radio_mem[OFFSET_BANK_1]; } // // Get channel by index. // static channel_t *get_channel(int i) { bank_t *b = get_bank(i >> 7); if ((b->bitmap[i % 128 / 8] >> (i & 7)) & 1) return &b->chan[i % 128]; else return 0; } // // Set the parameters for a given memory channel. // static void setup_channel(int i, int mode, char *name, double rx_mhz, double tx_mhz, int power, int scanlist, int squelch, int tot, int rxonly, int admit, int colorcode, int timeslot, int grouplist, int contact, int rxtone, int txtone, int width) { bank_t *b = get_bank(i >> 7); channel_t *ch = &b->chan[i % 128]; ch->channel_mode = mode; ch->bandwidth = width; ch->squelch = squelch; ch->rx_only = rxonly; ch->repeater_slot2 = (timeslot == 2); ch->colorcode_tx = colorcode; ch->colorcode_rx = colorcode; // ch->data_call_conf = 1; // Always ask for SMS acknowledge ch->power = power; ch->admit_criteria = admit; ch->contact_name_index = contact; ch->tot = tot; ch->scan_list_index = scanlist; ch->group_list_index = grouplist; ch->rx_frequency = mhz_to_abcdefgh(rx_mhz); ch->tx_frequency = mhz_to_abcdefgh(tx_mhz); ch->ctcss_dcs_receive = rxtone; ch->ctcss_dcs_transmit = txtone; ascii_decode(ch->name, name, sizeof(ch->name), 0xff); // Set valid bit. b->bitmap[i % 128 / 8] |= 1 << (i & 7); } // // Erase the channel record. // static void erase_channel(int i) { bank_t *b = get_bank(i >> 7); channel_t *ch = &b->chan[i % 128]; // Bytes 0-15 memset(ch->name, 0xff, sizeof(ch->name)); // Bytes 16-23 ch->rx_frequency = 0x40000000; ch->tx_frequency = 0x40000000; // Byte 24 ch->channel_mode = MODE_ANALOG; // Bytes 25-26 ch->_unused25[0] = 0; ch->_unused25[1] = 0; // Bytes 27-28 ch->tot = 0; ch->tot_rekey_delay = 5; // Byte 29 ch->admit_criteria = ADMIT_ALWAYS; // Bytes 30-31 ch->_unused30 = 0x50; ch->scan_list_index = 0; // Bytes 32-35 ch->ctcss_dcs_receive = 0xffff; ch->ctcss_dcs_transmit = 0xffff; // Bytes 36-39 ch->_unused36 = 0; ch->tx_signaling_syst = 0; ch->_unused38 = 0; ch->rx_signaling_syst = 0; // Bytes 40-43 ch->_unused40 = 0x16; ch->privacy_group = PRIVGR_NONE; ch->colorcode_tx = 1; ch->group_list_index = 0; // Bytes 44-47 ch->colorcode_rx = 1; ch->emergency_system_index = 0; ch->contact_name_index = 0; // Byte 48 ch->_unused48 = 0; ch->emergency_alarm_ack = 0; ch->data_call_conf = 0; // Byte 49 ch->private_call_conf = 0; ch->_unused49_1 = 0; ch->privacy = 0; ch->_unused49_5 = 0; ch->repeater_slot2 = 0; ch->_unused49_7 = 0; // Byte 50 ch->dcdm = 0; ch->_unused50_1 = 0; ch->non_ste_frequency = 0; ch->_unused50_6 = 0; // Byte 51 ch->bandwidth = BW_25_KHZ; ch->rx_only = 0; ch->talkaround = 0; ch->_unused51_4 = 0; ch->vox = 0; ch->power = POWER_HIGH; // Bytes 52-55 ch->_unused52[0] = 0; ch->_unused52[1] = 0; ch->_unused52[2] = 0; ch->_unused52[3] = 0; ch->squelch = SQ_NORMAL; // Clear valid bit. b->bitmap[i % 128 / 8] &= ~(1 << (i & 7)); } static void print_chanlist(FILE *out, uint16_t *unsorted, int nchan, int scanlist_flag) { int last = -1; int range = 0; int n; uint16_t data[nchan]; #define CNUM(n) (scanlist_flag ? n-1 : n) // Sort the list before printing. memcpy(data, unsorted, nchan * sizeof(uint16_t)); qsort(data, nchan, sizeof(uint16_t), compare_index); for (n=0; n 0) fprintf(out, ","); fprintf(out, "%d", CNUM(item)); } last = item; } if (range) fprintf(out, "-%d", CNUM(last)); } static void print_id(FILE *out, int verbose) { general_settings_t *gs = GET_SETTINGS(); unsigned id = GET_ID(gs->radio_id); if (verbose) fprintf(out, "\n# Unique DMR ID and name of this radio."); fprintf(out, "\nID: %u\nName: ", id); if (VALID_TEXT(gs->radio_name)) { print_ascii(out, gs->radio_name, 8, 0); } else { fprintf(out, "-"); } fprintf(out, "\n"); } static void print_intro(FILE *out, int verbose) { intro_text_t *it = GET_INTRO(); if (verbose) fprintf(out, "\n# Text displayed when the radio powers up.\n"); fprintf(out, "Intro Line 1: "); if (VALID_TEXT(it->intro_line1)) { print_ascii(out, it->intro_line1, 16, 0); } else { fprintf(out, "-"); } fprintf(out, "\nIntro Line 2: "); if (VALID_TEXT(it->intro_line2)) { print_ascii(out, it->intro_line2, 16, 0); } else { fprintf(out, "-"); } fprintf(out, "\n"); } // // Do we have any channels of given mode? // static int have_channels(int mode) { int i; for (i=0; ichannel_mode == mode) return 1; } return 0; } // // Print base parameters of the channel: // Name // RX Frequency // TX Frequency // Power // Scan List // TOT // RX Only // Admit Criteria // static void print_chan_base(FILE *out, channel_t *ch, int cnum) { fprintf(out, "%5d ", cnum); print_ascii(out, ch->name, 16, 1); fprintf(out, " "); print_freq(out, ch->rx_frequency); fprintf(out, " "); print_offset(out, ch->rx_frequency, ch->tx_frequency); fprintf(out, "%-4s ", POWER_NAME[ch->power]); if (ch->scan_list_index == 0) fprintf(out, "- "); else fprintf(out, "%-4d ", ch->scan_list_index); if (ch->tot == 0) fprintf(out, "- "); else fprintf(out, "%-3d ", ch->tot * 15); fprintf(out, "%c ", "-+"[ch->rx_only]); if (ch->channel_mode == MODE_DIGITAL) fprintf(out, "%-6s ", ADMIT_NAME[ch->admit_criteria & 3]); else fprintf(out, "%-6s ", ADMIT_NAME[ch->admit_criteria != 0]); } #ifdef PRINT_RARE_PARAMS // // Print extended parameters of the channel: // TOT Rekey Delay // VOX // Talkaround // static void print_chan_ext(FILE *out, channel_t *ch) { fprintf(out, "%-3d ", ch->tot_rekey_delay); fprintf(out, "%c ", "-+"[ch->vox]); fprintf(out, "%c ", "-+"[ch->talkaround]); } #endif static void print_digital_channels(FILE *out, int verbose) { int i; if (verbose) { fprintf(out, "# Table of digital channels.\n"); fprintf(out, "# 1) Channel number: 1-%d\n", NCHAN); fprintf(out, "# 2) Name: up to 16 characters, use '_' instead of space\n"); fprintf(out, "# 3) Receive frequency in MHz\n"); fprintf(out, "# 4) Transmit frequency or +/- offset in MHz\n"); fprintf(out, "# 5) Transmit power: High, Low\n"); fprintf(out, "# 6) Scan list: - or index in Scanlist table\n"); fprintf(out, "# 7) Transmit timeout timer in seconds: 0, 15, 30, 45... 555\n"); fprintf(out, "# 8) Receive only: -, +\n"); fprintf(out, "# 9) Admit criteria: -, Free, Color\n"); fprintf(out, "# 10) Color code: 0, 1, 2, 3... 15\n"); fprintf(out, "# 11) Time slot: 1 or 2\n"); fprintf(out, "# 12) Receive group list: - or index in Grouplist table\n"); fprintf(out, "# 13) Contact for transmit: - or index in Contacts table\n"); fprintf(out, "#\n"); } fprintf(out, "Digital Name Receive Transmit Power Scan TOT RO Admit Color Slot RxGL TxContact"); #ifdef PRINT_RARE_PARAMS fprintf(out, " Dly VOX TA EmSys Privacy PN PCC EAA DCC"); #endif fprintf(out, "\n"); for (i=0; ichannel_mode != MODE_DIGITAL) { // Select digital channels continue; } print_chan_base(out, ch, i+1); // Print digital parameters of the channel: // Color Code // Repeater Slot // Group List // Contact Name fprintf(out, "%-5d %-3d ", ch->colorcode_tx, ch->repeater_slot2 + 1); if (ch->group_list_index == 0) fprintf(out, "- "); else fprintf(out, "%-4d ", ch->group_list_index); if (ch->contact_name_index == 0) fprintf(out, "-"); else fprintf(out, "%-4d", ch->contact_name_index); #ifdef PRINT_RARE_PARAMS fprintf(out, " "); print_chan_ext(out, ch); // Extended digital parameters of the channel: // Emergency System // Privacy // Privacy Group // Private Call Confirmed // Emergency Alarm Ack // Data Call Confirmed // DCDM switch (inverted) // Leader/MS if (ch->emergency_system_index == 0) fprintf(out, "- "); else fprintf(out, "%-5d ", ch->emergency_system_index); fprintf(out, "%-8s ", PRIVACY_NAME[ch->privacy]); if (ch->privacy == 0) fprintf(out, "- "); else fprintf(out, "%-2d ", ch->privacy_group); fprintf(out, "%c ", "-+"[ch->private_call_conf]); fprintf(out, "%c ", "-+"[ch->emergency_alarm_ack]); fprintf(out, "%c ", "-+"[ch->data_call_conf]); #endif // Print contact name as a comment. if (ch->contact_name_index > 0) { contact_t *ct = GET_CONTACT(ch->contact_name_index - 1); if (VALID_CONTACT(ct)) { fprintf(out, " # "); print_ascii(out, ct->name, 16, 0); } } fprintf(out, "\n"); } } static void print_analog_channels(FILE *out, int verbose) { int i; if (verbose) { fprintf(out, "# Table of analog channels.\n"); fprintf(out, "# 1) Channel number: 1-%d\n", NCHAN); fprintf(out, "# 2) Name: up to 16 characters, use '_' instead of space\n"); fprintf(out, "# 3) Receive frequency in MHz\n"); fprintf(out, "# 4) Transmit frequency or +/- offset in MHz\n"); fprintf(out, "# 5) Transmit power: High, Low\n"); fprintf(out, "# 6) Scan list: - or index\n"); fprintf(out, "# 7) Transmit timeout timer in seconds: 0, 15, 30, 45... 555\n"); fprintf(out, "# 8) Receive only: -, +\n"); fprintf(out, "# 9) Admit criteria: -, Free, Tone\n"); fprintf(out, "# 10) Squelch level: Normal, Tight\n"); fprintf(out, "# 11) Guard tone for receive, or '-' to disable\n"); fprintf(out, "# 12) Guard tone for transmit, or '-' to disable\n"); fprintf(out, "# 13) Bandwidth in kHz: 12.5, 20, 25\n"); fprintf(out, "#\n"); } fprintf(out, "Analog Name Receive Transmit Power Scan TOT RO Admit Squelch RxTone TxTone Width"); #ifdef PRINT_RARE_PARAMS fprintf(out, " Dly VOX TA RxSign TxSign"); #endif fprintf(out, "\n"); for (i=0; ichannel_mode != MODE_ANALOG) { // Select analog channels continue; } print_chan_base(out, ch, i+1); // Print analog parameters of the channel: // Squelch // CTCSS/DCS Dec // CTCSS/DCS Enc // Bandwidth fprintf(out, "%-7s ", SQUELCH_NAME[ch->squelch]); print_tone(out, ch->ctcss_dcs_receive); fprintf(out, " "); print_tone(out, ch->ctcss_dcs_transmit); fprintf(out, " %-6s", BANDWIDTH[ch->bandwidth]); #ifdef PRINT_RARE_PARAMS print_chan_ext(out, ch); // Extended analog parameters of the channel: // Rx Signaling System // Tx Signaling System fprintf(out, "%-6s ", SIGNALING_SYSTEM[ch->rx_signaling_syst & 1]); fprintf(out, "%-6s ", SIGNALING_SYSTEM[ch->tx_signaling_syst & 1]); #endif fprintf(out, "\n"); } } static int have_zones() { zonetab_t *zt = GET_ZONETAB(); int i; for (i=0; i<32; i++) { if (zt->bitmap[i]) return 1; } return 0; } static int have_scanlists() { scantab_t *st = GET_SCANTAB(); int i; for (i=0; ivalid[i]) return 1; } return 0; } static int have_contacts() { int i; for (i=0; initems1[i] > 0) return 1; } return 0; } static int have_messages() { msgtab_t *mt = GET_MSGTAB(); return mt->count > 0; } // // Print full information about the device configuration. // static void gd77_print_config(radio_device_t *radio, FILE *out, int verbose) { int i; fprintf(out, "Radio: %s\n", radio->name); if (verbose) gd77_print_version(radio, out); // // Channels. // if (have_channels(MODE_DIGITAL)) { fprintf(out, "\n"); print_digital_channels(out, verbose); } if (have_channels(MODE_ANALOG)) { fprintf(out, "\n"); print_analog_channels(out, verbose); } // // Zones. // if (have_zones()) { fprintf(out, "\n"); if (verbose) { fprintf(out, "# Table of channel zones.\n"); fprintf(out, "# 1) Zone number: 1-%d\n", NZONES); fprintf(out, "# 2) Name: up to 16 characters, use '_' instead of space\n"); fprintf(out, "# 3) List of channels: numbers and ranges (N-M) separated by comma\n"); fprintf(out, "#\n"); } fprintf(out, "Zone Name Channels\n"); for (i=0; iname, 16, 1); fprintf(out, " "); if (z->member[0]) { print_chanlist(out, z->member, 16, 0); } else { fprintf(out, "-"); } fprintf(out, "\n"); } } // // Scan lists. // if (have_scanlists()) { fprintf(out, "\n"); if (verbose) { fprintf(out, "# Table of scan lists.\n"); fprintf(out, "# 1) Scan list number: 1-%d\n", NSCANL); fprintf(out, "# 2) Name: up to 16 characters, use '_' instead of space\n"); fprintf(out, "# 3) Priority channel 1 (50%% of scans): -, Sel or index\n"); fprintf(out, "# 4) Priority channel 2 (25%% of scans): -, Sel or index\n"); fprintf(out, "# 5) Designated transmit channel: Last, Sel or index\n"); fprintf(out, "# 6) List of channels: numbers and ranges (N-M) separated by comma\n"); fprintf(out, "#\n"); } fprintf(out, "Scanlist Name PCh1 PCh2 TxCh "); #ifdef PRINT_RARE_PARAMS fprintf(out, "Hold Smpl "); #endif fprintf(out, "Channels\n"); for (i=0; iname, 15, 1); if (sl->priority_ch1 == 0) { fprintf(out, " - "); } else if (sl->priority_ch1 == 1) { fprintf(out, " Sel "); } else { fprintf(out, " %-4d ", sl->priority_ch1 - 1); } if (sl->priority_ch2 == 0) { fprintf(out, "- "); } else if (sl->priority_ch2 == 1) { fprintf(out, "Sel "); } else { fprintf(out, "%-4d ", sl->priority_ch2 - 1); } if (sl->tx_designated_ch == 0) { fprintf(out, "Last "); } else if (sl->tx_designated_ch == 1) { fprintf(out, "Sel "); } else { fprintf(out, "%-4d ", sl->tx_designated_ch - 1); } #ifdef PRINT_RARE_PARAMS fprintf(out, "%-4d %-4d ", sl->sign_hold_time * 25, sl->prio_sample_time * 250); #endif if (sl->member[1]) { print_chanlist(out, sl->member + 1, 31, 1); } else { fprintf(out, "Sel"); } fprintf(out, "\n"); } } // // Contacts. // if (have_contacts()) { fprintf(out, "\n"); if (verbose) { fprintf(out, "# Table of contacts.\n"); fprintf(out, "# 1) Contact number: 1-%d\n", NCONTACTS); fprintf(out, "# 2) Name: up to 16 characters, use '_' instead of space\n"); fprintf(out, "# 3) Call type: Group, Private, All\n"); fprintf(out, "# 4) Call ID: 1...16777215\n"); fprintf(out, "# 5) Call receive tone: -, +\n"); fprintf(out, "#\n"); } fprintf(out, "Contact Name Type ID RxTone\n"); for (i=0; iname, 16, 1); fprintf(out, " %-7s %-8d %s\n", CONTACT_TYPE[ct->type & 3], CONTACT_ID(ct), ct->receive_tone ? "+" : "-"); } } // // Group lists. // if (have_grouplists()) { fprintf(out, "\n"); if (verbose) { fprintf(out, "# Table of group lists.\n"); fprintf(out, "# 1) Group list number: 1-%d\n", NGLISTS); fprintf(out, "# 2) Name: up to 16 characters, use '_' instead of space\n"); fprintf(out, "# 3) List of contacts: numbers and ranges (N-M) separated by comma\n"); fprintf(out, "#\n"); } fprintf(out, "Grouplist Name Contacts\n"); for (i=0; iname, 16, 1); fprintf(out, " "); if (gl->member[0]) { print_chanlist(out, gl->member, 32, 0); } else { fprintf(out, "-"); } fprintf(out, "\n"); } } // // Text messages. // if (have_messages()) { msgtab_t *mt = GET_MSGTAB(); fprintf(out, "\n"); if (verbose) { fprintf(out, "# Table of text messages.\n"); fprintf(out, "# 1) Message number: 1-%d\n", NMESSAGES); fprintf(out, "# 2) Text: up to 144 characters\n"); fprintf(out, "#\n"); } fprintf(out, "Message Text\n"); for (i=0; ilen[i] == 0) { // Message is disabled continue; } fprintf(out, "%5d ", i+1); print_ascii(out, &mt->message[i*144], 144, 0); fprintf(out, "\n"); } } // General settings. print_id(out, verbose); print_intro(out, verbose); } // // Read memory image from the binary file. // static void gd77_read_image(radio_device_t *radio, FILE *img) { 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; default: fprintf(stderr, "Unrecognized file size %u bytes.\n", (int) st.st_size); exit(-1); } } // // Save memory image to the binary file. // static void gd77_save_image(radio_device_t *radio, FILE *img) { fwrite(&radio_mem[0], 1, MEMSZ, img); } // // Erase all channels. // static void erase_channels() { int i; for (i=0; iradio_name, value, 8, 0xff); return; } if (strcasecmp ("ID", param) == 0) { uint32_t id = strtoul(value, 0, 0); gs->radio_id[0] = ((id / 10000000) << 4) | ((id / 1000000) % 10); gs->radio_id[1] = ((id / 100000 % 10) << 4) | ((id / 10000) % 10); gs->radio_id[2] = ((id / 1000 % 10) << 4) | ((id / 100) % 10); gs->radio_id[3] = ((id / 10 % 10) << 4) | (id % 10); return; } if (strcasecmp ("Last Programmed Date", param) == 0) { // Ignore. return; } if (strcasecmp ("CPS Software Version", param) == 0) { // Ignore. return; } intro_text_t *it = GET_INTRO(); if (strcasecmp ("Intro Line 1", param) == 0) { ascii_decode(it->intro_line1, value, 16, 0xff); return; } if (strcasecmp ("Intro Line 2", param) == 0) { ascii_decode(it->intro_line2, value, 16, 0xff); return; } fprintf(stderr, "Unknown parameter: %s = %s\n", param, value); exit(-1); } // // Parse one line of Digital channel table. // Start_flag is 1 for the first table row. // Return 0 on failure. // static int parse_digital_channel(radio_device_t *radio, int first_row, char *line) { char num_str[256], name_str[256], rxfreq_str[256], offset_str[256]; char power_str[256], scanlist_str[256]; char tot_str[256], rxonly_str[256], admit_str[256], colorcode_str[256]; char slot_str[256], grouplist_str[256], contact_str[256]; int num, power, scanlist, tot, rxonly, admit; int colorcode, timeslot, grouplist, contact; double rx_mhz, tx_mhz; if (sscanf(line, "%s %s %s %s %s %s %s %s %s %s %s %s %s", num_str, name_str, rxfreq_str, offset_str, power_str, scanlist_str, tot_str, rxonly_str, admit_str, colorcode_str, slot_str, grouplist_str, contact_str) != 13) return 0; num = atoi(num_str); if (num < 1 || num > NCHAN) { fprintf(stderr, "Bad channel number.\n"); return 0; } if (sscanf(rxfreq_str, "%lf", &rx_mhz) != 1 || !is_valid_frequency(rx_mhz)) { fprintf(stderr, "Bad receive frequency.\n"); return 0; } if (sscanf(offset_str, "%lf", &tx_mhz) != 1) { badtx: fprintf(stderr, "Bad transmit frequency.\n"); return 0; } if (offset_str[0] == '-' || offset_str[0] == '+') tx_mhz += rx_mhz; if (! is_valid_frequency(tx_mhz)) goto badtx; if (strcasecmp("High", power_str) == 0) { power = POWER_HIGH; } else if (strcasecmp("Low", power_str) == 0) { power = POWER_LOW; } else { fprintf(stderr, "Bad power level.\n"); return 0; } if (*scanlist_str == '-') { scanlist = 0; } else { scanlist = atoi(scanlist_str); if (scanlist == 0 || scanlist > NSCANL) { fprintf(stderr, "Bad scanlist.\n"); return 0; } } tot = atoi(tot_str); if (tot > 555 || tot % 15 != 0) { fprintf(stderr, "Bad timeout timer.\n"); return 0; } tot /= 15; if (*rxonly_str == '-') { rxonly = 0; } else if (*rxonly_str == '+') { rxonly = 1; } else { fprintf(stderr, "Bad receive only flag.\n"); return 0; } if (*admit_str == '-' || strcasecmp("Always", admit_str) == 0) { admit = ADMIT_ALWAYS; } else if (strcasecmp("Free", admit_str) == 0) { admit = ADMIT_CH_FREE; } else if (strcasecmp("Color", admit_str) == 0) { admit = ADMIT_COLOR; } else { fprintf(stderr, "Bad admit criteria.\n"); return 0; } colorcode = atoi(colorcode_str); if (colorcode < 0 || colorcode > 15) { fprintf(stderr, "Bad color code.\n"); return 0; } timeslot = atoi(slot_str); if (timeslot < 1 || timeslot > 2) { fprintf(stderr, "Bad timeslot.\n"); return 0; } if (*grouplist_str == '-') { grouplist = 0; } else { grouplist = atoi(grouplist_str); if (grouplist == 0 || grouplist > NGLISTS) { fprintf(stderr, "Bad receive grouplist.\n"); return 0; } } if (*contact_str == '-') { contact = 0; } else { contact = atoi(contact_str); if (contact == 0 || contact > NCONTACTS) { fprintf(stderr, "Bad transmit contact.\n"); return 0; } } if (first_row && radio->channel_count == 0) { // On first entry, erase all channels, zones and scanlists. erase_channels(); erase_zones(); erase_scanlists(); } setup_channel(num-1, MODE_DIGITAL, name_str, rx_mhz, tx_mhz, power, scanlist, 5, tot, rxonly, admit, colorcode, timeslot, grouplist, contact, 0xffff, 0xffff, BW_12_5_KHZ); radio->channel_count++; return 1; } // // Parse one line of Analog channel table. // Start_flag is 1 for the first table row. // Return 0 on failure. // static int parse_analog_channel(radio_device_t *radio, int first_row, char *line) { char num_str[256], name_str[256], rxfreq_str[256], offset_str[256]; char power_str[256], scanlist_str[256], squelch_str[256]; char tot_str[256], rxonly_str[256], admit_str[256]; char rxtone_str[256], txtone_str[256], width_str[256]; int num, power, scanlist, squelch, tot, rxonly, admit; int rxtone, txtone, width; double rx_mhz, tx_mhz; if (sscanf(line, "%s %s %s %s %s %s %s %s %s %s %s %s %s", num_str, name_str, rxfreq_str, offset_str, power_str, scanlist_str, tot_str, rxonly_str, admit_str, squelch_str, rxtone_str, txtone_str, width_str) != 13) return 0; num = atoi(num_str); if (num < 1 || num > NCHAN) { fprintf(stderr, "Bad channel number.\n"); return 0; } if (sscanf(rxfreq_str, "%lf", &rx_mhz) != 1 || !is_valid_frequency(rx_mhz)) { fprintf(stderr, "Bad receive frequency.\n"); return 0; } if (sscanf(offset_str, "%lf", &tx_mhz) != 1) { badtx: fprintf(stderr, "Bad transmit frequency.\n"); return 0; } if (offset_str[0] == '-' || offset_str[0] == '+') tx_mhz += rx_mhz; if (! is_valid_frequency(tx_mhz)) goto badtx; if (strcasecmp("High", power_str) == 0) { power = POWER_HIGH; } else if (strcasecmp("Low", power_str) == 0) { power = POWER_LOW; } else { fprintf(stderr, "Bad power level.\n"); return 0; } if (*scanlist_str == '-') { scanlist = 0; } else { scanlist = atoi(scanlist_str); if (scanlist == 0 || scanlist > NSCANL) { fprintf(stderr, "Bad scanlist.\n"); return 0; } } if (strcasecmp ("Normal", squelch_str) == 0) { squelch = SQ_NORMAL; } else if (strcasecmp ("Tight", squelch_str) == 0) { squelch = SQ_TIGHT; } else { fprintf (stderr, "Bad squelch level.\n"); return 0; } tot = atoi(tot_str); if (tot > 555 || tot % 15 != 0) { fprintf(stderr, "Bad timeout timer.\n"); return 0; } tot /= 15; if (*rxonly_str == '-') { rxonly = 0; } else if (*rxonly_str == '+') { rxonly = 1; } else { fprintf(stderr, "Bad receive only flag.\n"); return 0; } if (*admit_str == '-' || strcasecmp("Always", admit_str) == 0) { admit = ADMIT_ALWAYS; } else if (strcasecmp("Free", admit_str) == 0) { admit = ADMIT_CH_FREE; } else { fprintf(stderr, "Bad admit criteria.\n"); return 0; } rxtone = encode_tone(rxtone_str); if (rxtone < 0) { fprintf(stderr, "Bad receive tone.\n"); return 0; } txtone = encode_tone(txtone_str); if (txtone < 0) { fprintf(stderr, "Bad transmit tone.\n"); return 0; } if (strcasecmp ("12.5", width_str) == 0) { width = BW_12_5_KHZ; } else if (strcasecmp ("25", width_str) == 0) { width = BW_25_KHZ; } else { fprintf (stderr, "Bad width.\n"); return 0; } if (first_row && radio->channel_count == 0) { // On first entry, erase all channels, zones and scanlists. erase_channels(); erase_zones(); erase_scanlists(); } setup_channel(num-1, MODE_ANALOG, name_str, rx_mhz, tx_mhz, power, scanlist, squelch, tot, rxonly, admit, 0, 1, 0, 0, rxtone, txtone, width); radio->channel_count++; return 1; } // // Parse one line of Zones table. // Return 0 on failure. // static int parse_zones(int first_row, char *line) { char num_str[256], name_str[256], chan_str[256]; int znum; if (sscanf(line, "%s %s %s", num_str, name_str, chan_str) != 3) return 0; znum = strtoul(num_str, 0, 10); if (znum < 1 || znum > NZONES) { fprintf(stderr, "Bad zone number.\n"); return 0; } if (first_row) { // On first entry, erase the Zones table. erase_zones(); } setup_zone(znum-1, name_str); if (*chan_str != '-') { char *str = chan_str; int nchan = 0; int range = 0; int last = 0; // Parse channel list. for (;;) { char *eptr; int cnum = strtoul(str, &eptr, 10); if (eptr == str) { fprintf(stderr, "Zone %d: wrong channel list '%s'.\n", znum, str); return 0; } if (cnum < 1 || cnum > NCHAN) { fprintf(stderr, "Zone %d: wrong channel number %d.\n", znum, cnum); return 0; } if (range) { // Add range. int c; for (c=last+1; c<=cnum; c++) { if (!zone_append(znum-1, c)) { fprintf(stderr, "Zone %d: too many channels.\n", znum); return 0; } nchan++; } } else { // Add single channel. if (!zone_append(znum-1, cnum)) { fprintf(stderr, "Zone %d: too many channels.\n", znum); return 0; } nchan++; } if (*eptr == 0) break; if (*eptr != ',' && *eptr != '-') { fprintf(stderr, "Zone %d: wrong channel list '%s'.\n", znum, eptr); return 0; } range = (*eptr == '-'); last = cnum; str = eptr + 1; } } return 1; } // // Parse one line of Scanlist table. // Return 0 on failure. // static int parse_scanlist(int first_row, char *line) { char num_str[256], name_str[256], prio1_str[256], prio2_str[256]; char tx_str[256], chan_str[256]; int snum, prio1, prio2, txchan; if (sscanf(line, "%s %s %s %s %s %s", num_str, name_str, prio1_str, prio2_str, tx_str, chan_str) != 6) return 0; snum = atoi(num_str); if (snum < 1 || snum > NSCANL) { fprintf(stderr, "Bad scan list number.\n"); return 0; } if (first_row) { // On first entry, erase the Scanlists table. erase_scanlists(); } if (*prio1_str == '-') { prio1 = 0; } else if (strcasecmp("Sel", prio1_str) == 0) { prio1 = 1; } else { prio1 = atoi(prio1_str); if (prio1 < 1 || prio1 > NCHAN) { fprintf(stderr, "Bad priority channel 1.\n"); return 0; } prio1++; } if (*prio2_str == '-') { prio2 = 0; } else if (strcasecmp("Sel", prio2_str) == 0) { prio2 = 1; } else { prio2 = atoi(prio2_str); if (prio2 < 1 || prio2 > NCHAN) { fprintf(stderr, "Bad priority channel 2.\n"); return 0; } prio2++; } if (strcasecmp("Last", tx_str) == 0) { txchan = 0; } else if (strcasecmp("Sel", tx_str) == 0) { txchan = 1; } else { txchan = atoi(tx_str); if (txchan < 1 || txchan > NCHAN) { fprintf(stderr, "Bad transmit channel.\n"); return 0; } txchan++; } setup_scanlist(snum-1, name_str, prio1, prio2, txchan); if (*chan_str == '-') { // Empty. } else if (strcasecmp("Sel", chan_str) == 0) { // Selected channel only. scanlist_append(snum-1, 0); } else { char *str = chan_str; int nchan = 0; int range = 0; int last = 0; // Parse channel list. for (;;) { char *eptr; int cnum = strtoul(str, &eptr, 10); if (eptr == str) { fprintf(stderr, "Scan list %d: wrong channel list '%s'.\n", snum, str); return 0; } if (cnum < 1 || cnum > NCHAN) { fprintf(stderr, "Scan list %d: wrong channel number %d.\n", snum, cnum); return 0; } if (range) { // Add range. int c; for (c=last+1; c<=cnum; c++) { if (!scanlist_append(snum-1, c)) { fprintf(stderr, "Scan list %d: too many channels.\n", snum); return 0; } nchan++; } } else { // Add single channel. if (!scanlist_append(snum-1, cnum)) { fprintf(stderr, "Scan list %d: too many channels.\n", snum); return 0; } nchan++; } if (*eptr == 0) break; if (*eptr != ',' && *eptr != '-') { fprintf(stderr, "Scan list %d: wrong channel list '%s'.\n", snum, eptr); return 0; } range = (*eptr == '-'); last = cnum; str = eptr + 1; } } return 1; } // // Parse one line of Contacts table. // Return 0 on failure. // static int parse_contact(int first_row, char *line) { char num_str[256], name_str[256], type_str[256], id_str[256], rxtone_str[256]; int cnum, type, id, rxtone; if (sscanf(line, "%s %s %s %s %s", num_str, name_str, type_str, id_str, rxtone_str) != 5) return 0; cnum = atoi(num_str); if (cnum < 1 || cnum > NCONTACTS) { fprintf(stderr, "Bad contact number.\n"); return 0; } if (first_row) { // On first entry, erase the Contacts table. erase_contacts(); } if (strcasecmp("Group", type_str) == 0) { type = CALL_GROUP; } else if (strcasecmp("Private", type_str) == 0) { type = CALL_PRIVATE; } else if (strcasecmp("All", type_str) == 0) { type = CALL_ALL; } else { fprintf(stderr, "Bad call type.\n"); return 0; } id = atoi(id_str); if (id < 1 || id > 0xffffff) { fprintf(stderr, "Bad call ID.\n"); return 0; } if (*rxtone_str == '-' || strcasecmp("No", rxtone_str) == 0) { rxtone = 0; } else if (*rxtone_str == '+' || strcasecmp("Yes", rxtone_str) == 0) { rxtone = 1; } else { fprintf(stderr, "Bad receive tone flag.\n"); return 0; } setup_contact(cnum-1, name_str, type, id, rxtone); return 1; } // // Parse one line of Grouplist table. // Return 0 on failure. // static int parse_grouplist(int first_row, char *line) { char num_str[256], name_str[256], list_str[256]; int glnum; if (sscanf(line, "%s %s %s", num_str, name_str, list_str) != 3) return 0; glnum = strtoul(num_str, 0, 10); if (glnum < 1 || glnum > NGLISTS) { fprintf(stderr, "Bad group list number.\n"); return 0; } if (first_row) { // On first entry, erase the Grouplists table. memset(&radio_mem[OFFSET_GROUPTAB], 0, sizeof(grouptab_t)); } setup_grouplist(glnum-1, name_str); if (*list_str != '-') { char *str = list_str; int range = 0; int last = 0; // Parse contact list. for (;;) { char *eptr; int cnum = strtoul(str, &eptr, 10); if (eptr == str) { fprintf(stderr, "Group list %d: wrong contact list '%s'.\n", glnum, str); return 0; } if (cnum < 1 || cnum > NCONTACTS) { fprintf(stderr, "Group list %d: wrong contact number %d.\n", glnum, cnum); return 0; } if (range) { // Add range. int c; for (c=last+1; c<=cnum; c++) { if (!grouplist_append(glnum-1, c)) { fprintf(stderr, "Group list %d: too many contacts.\n", glnum); return 0; } } } else { // Add single contact. if (!grouplist_append(glnum-1, cnum)) { fprintf(stderr, "Group list %d: too many contacts.\n", glnum); return 0; } } if (*eptr == 0) break; if (*eptr != ',' && *eptr != '-') { fprintf(stderr, "Group list %d: wrong contact list '%s'.\n", glnum, eptr); return 0; } range = (*eptr == '-'); last = cnum; str = eptr + 1; } } return 1; } // // Parse one line of Messages table. // Return 0 on failure. // static int parse_messages(int first_row, char *line) { char *text; int mnum; mnum = strtoul(line, &text, 10); if (text == line || mnum < 1 || mnum > NMESSAGES) { fprintf(stderr, "Bad message number.\n"); return 0; } if (first_row) { // On first entry, erase the Messages table. memset(GET_MSGTAB(), 0, sizeof(msgtab_t)); } setup_message(mnum-1, text); return 1; } // // Parse table header. // Return table id, or 0 in case of error. // static int gd77_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 gd77_parse_row(radio_device_t *radio, int table_id, int first_row, char *line) { 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); } return 0; } // // Update timestamp. // static void gd77_update_timestamp(radio_device_t *radio) { 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 } // // Check that configuration is correct. // Return 0 on error. // static int gd77_verify_config(radio_device_t *radio) { 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 (!sl) { fprintf(stderr, "Channel %d '", i+1); print_ascii(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_ascii(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 (!gl) { fprintf(stderr, "Channel %d '", i+1); print_ascii(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[k]; if (cnum != 0 && !get_channel(cnum - 1)) { fprintf(stderr, "Zone %d '", i+1); print_ascii(stderr, z->name, 16, 0); fprintf(stderr, "': channel %d not found.\n", cnum); nerrors++; } } } // Scanlists: check references to channels. for (i=0; imember[k] - 1; if (cnum > 0 && !get_channel(cnum - 1)) { fprintf(stderr, "Scanlist %d '", i+1); print_ascii(stderr, sl->name, 15, 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_ascii(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); return 1; } // // Radioddity GD-77, version 3.1.1 and later // radio_device_t radio_gd77 = { "Radioddity GD-77", gd77_download, gd77_upload, gd77_is_compatible, gd77_read_image, gd77_save_image, gd77_print_version, gd77_print_config, gd77_verify_config, gd77_parse_parameter, gd77_parse_header, gd77_parse_row, gd77_update_timestamp, //TODO: gd77_write_csv, };