diff --git a/Makefile b/Makefile index 8183fbb..4fd67a1 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ CC = gcc -VERSION = 0.7 +VERSION = $(shell git describe --tags --abbrev=0) GITCOUNT = $(shell git rev-list HEAD --count) 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 +OBJS = main.o util.o radio.o dfu-libusb.o uv380.o md380.o rd5r.o LIBS = -lusb-1.0 # Linux @@ -31,8 +31,11 @@ dmrconfig.linux: dmrconfig ### dfu-libusb.o: dfu-libusb.c util.h +dfu-windows.o: dfu-windows.c util.h +hid-libusb.o: hid-libusb.c util.h 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 util.o: util.c util.h uv380.o: uv380.c radio.h util.h diff --git a/Makefile-mingw b/Makefile-mingw index f282624..1cd724b 100644 --- a/Makefile-mingw +++ b/Makefile-mingw @@ -1,6 +1,6 @@ CC = gcc -VERSION = 0.7 +VERSION = $(shell git describe --tags --abbrev=0) GITCOUNT = $(shell git rev-list HEAD --count) CFLAGS = -g -O -Wall -Werror -DVERSION='"$(VERSION).$(GITCOUNT)"' LDFLAGS = -g -s diff --git a/radio.c b/radio.c index 57fbf8d..8669b95 100644 --- a/radio.c +++ b/radio.c @@ -47,6 +47,7 @@ static struct { { "ZD3688", &radio_d900 }, // Zastone D900 { "TP660", &radio_dp880 }, // Zastone DP880 { "ZN><:", &radio_rt27d }, // Radtel RT-27D + { "BF-5R", &radio_rd5r }, // Baofengl RD-5R { 0, 0 } }; @@ -181,6 +182,9 @@ void radio_read_image(const char *filename) case 262709: device = &radio_md380; break; + case 131072: + device = &radio_rd5r; + break; default: fprintf(stderr, "%s: Unrecognized file size %u bytes.\n", filename, (int) st.st_size); diff --git a/radio.h b/radio.h index eebf9c5..1276c52 100644 --- a/radio.h +++ b/radio.h @@ -122,6 +122,7 @@ extern radio_device_t radio_md9600; // TYT MD-9600 extern radio_device_t radio_d900; // Zastone D900 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 // // Radio: memory contents. diff --git a/rd5r.c b/rd5r.c new file mode 100644 index 0000000..9acd9c7 --- /dev/null +++ b/rd5r.c @@ -0,0 +1,2284 @@ +/* + * Interface to Baofeng RD-5R. + * + * 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 1000 //TODO +#define NCONTACTS 1000 //TODO +#define NZONES 250 //TODO +#define NGLISTS 250 //TODO +#define NSCANL 250 //TODO +#define NMESSAGES 50 //TODO + +#define MEMSZ 0x20000 +#define OFFSET_TIMESTMP 0x02001 //TODO +#define OFFSET_SETTINGS 0x02040 //TODO +#define OFFSET_MSG 0x02180 //TODO +#define OFFSET_CONTACTS 0x05f80 //TODO +#define OFFSET_GLISTS 0x0ec20 //TODO +#define OFFSET_ZONES 0x149e0 //TODO +#define OFFSET_SCANL 0x18860 //TODO +#define OFFSET_CHANNELS 0x1ee00 //TODO + +#define GET_TIMESTAMP() (&radio_mem[OFFSET_TIMESTMP]) +#define GET_SETTINGS() ((general_settings_t*) &radio_mem[OFFSET_SETTINGS]) +#define GET_CHANNEL(i) ((channel_t*) &radio_mem[OFFSET_CHANNELS + (i)*64]) +#define GET_ZONE(i) ((zone_t*) &radio_mem[OFFSET_ZONES + (i)*64]) +#define GET_ZONEXT(i) ((zone_ext_t*) &radio_mem[OFFSET_ZONEXT + (i)*224]) +#define GET_SCANLIST(i) ((scanlist_t*) &radio_mem[OFFSET_SCANL + (i)*104]) +#define GET_CONTACT(i) ((contact_t*) &radio_mem[OFFSET_CONTACTS + (i)*36]) +#define GET_GROUPLIST(i) ((grouplist_t*) &radio_mem[OFFSET_GLISTS + (i)*96]) +#define GET_MESSAGE(i) ((uint16_t*) &radio_mem[OFFSET_MSG + (i)*288]) + +#define VALID_TEXT(txt) (*(txt) != 0 && *(txt) != 0xffff) +#define VALID_CHANNEL(ch) VALID_TEXT((ch)->name) +#define VALID_ZONE(z) VALID_TEXT((z)->name) +#define VALID_SCANLIST(sl) VALID_TEXT((sl)->name) +#define VALID_GROUPLIST(gl) VALID_TEXT((gl)->name) +#define VALID_CONTACT(ct) ((ct)->type != 0 && VALID_TEXT((ct)->name)) + +// +// Channel data. +// +typedef struct { + // Byte 0 + uint8_t channel_mode : 2, // Mode: Analog or Digital +#define MODE_ANALOG 1 +#define MODE_DIGITAL 2 + + bandwidth : 2, // Bandwidth: 12.5 or 20 or 25 kHz +#define BW_12_5_KHZ 0 +#define BW_20_KHZ 1 +#define BW_25_KHZ 2 + + autoscan : 1, // Autoscan Enable + squelch : 1, // Squelch +#define SQ_TIGHT 0 +#define SQ_NORMAL 1 + + _unused1 : 1, // 1 + lone_worker : 1; // Lone Worker + + // Byte 1 + uint8_t talkaround : 1, // Allow Talkaround + rx_only : 1, // RX Only Enable + repeater_slot : 2, // Repeater Slot: 1 or 2 + colorcode : 4; // Color Code: 0...15 + + // Byte 2 + uint8_t privacy_no : 4, // Privacy No. (+1): 1...16 + privacy : 2, // Privacy: None, Basic or Enhanced +#define PRIV_NONE 0 +#define PRIV_BASIC 1 +#define PRIV_ENHANCED 2 + + private_call_conf : 1, // Private Call Confirmed + data_call_conf : 1; // Data Call Confirmed + + // Byte 3 + uint8_t rx_ref_frequency : 2, // RX Ref Frequency: Low, Medium or High +#define REF_LOW 0 +#define REF_MEDIUM 1 +#define REF_HIGH 2 + + _unused2 : 1, // 0 + emergency_alarm_ack : 1, // Emergency Alarm Ack + _unused3 : 2, // 0b10 + uncompressed_udp : 1, // Compressed UDP Data header (0) Enable, (1) Disable + display_pttid_dis : 1; // Display PTT ID (inverted) + + // Byte 4 + uint8_t tx_ref_frequency : 2, // RX Ref Frequency: Low, Medium or High + _unused4 : 2, // 0b01 + vox : 1, // VOX Enable + power : 1, // Power: Low, High +#define POWER_HIGH 1 +#define POWER_LOW 0 + + admit_criteria : 2; // Admit Criteria: Always, Channel Free or Correct CTS/DCS +#define ADMIT_ALWAYS 0 +#define ADMIT_CH_FREE 1 +#define ADMIT_TONE 2 +#define ADMIT_COLOR 3 + + // Byte 5 + uint8_t _unused5 : 4, // 0 + in_call_criteria : 2, // In Call Criteria: Always, Follow Admit Criteria or TX Interrupt +#define INCALL_ALWAYS 0 +#define INCALL_ADMIT 1 + + _unused6 : 2; // 0b11 + + // Bytes 6-7 + uint16_t contact_name_index; // Contact Name: Contact1... + + // Bytes 8-9 + uint8_t tot : 6, // TOT x 15sec: 0-Infinite, 1=15s... 37=555s + _unused13 : 2; // 0 + uint8_t tot_rekey_delay; // TOT Rekey Delay: 0s...255s + + // Bytes 10-11 + uint8_t emergency_system_index; // Emergency System: None, System1...32 + uint8_t scan_list_index; // Scan List: None, ScanList1...250 + + // Bytes 12-13 + uint8_t group_list_index; // Group List: None, GroupList1...250 + uint8_t _unused7; // 0 + + // Bytes 14-15 + uint8_t _unused8; // 0 + uint8_t _unused9; // 0xff + + // Bytes 16-23 + uint32_t rx_frequency; // RX Frequency: 8 digits BCD + uint32_t tx_frequency; // TX Frequency: 8 digits BCD + + // Bytes 24-27 + uint16_t ctcss_dcs_receive; // CTCSS/DCS Dec: 4 digits BCD + uint16_t ctcss_dcs_transmit; // CTCSS/DCS Enc: 4 digits BCD + + // Bytes 28-29 + uint8_t rx_signaling_syst; // Rx Signaling System: Off, DTMF-1...4 + uint8_t tx_signaling_syst; // Tx Signaling System: Off, DTMF-1...4 + + // Bytes 30-31 + uint8_t _unused10; // 0xff + uint8_t _unused11; // 0xff + + // Bytes 32-63 + uint16_t name[16]; // Channel Name (Unicode) +} channel_t; + +// +// Contact data. +// +typedef struct { + // Bytes 0-2 + uint8_t id[3]; // Call ID: 1...16777215 +#define CONTACT_ID(ct) ((ct)->id[0] | ((ct)->id[1] << 8) | ((ct)->id[2] << 16)) + + // Byte 3 + uint8_t type : 5, // Call Type: Group Call, Private Call or All Call +#define CALL_GROUP 1 +#define CALL_PRIVATE 2 +#define CALL_ALL 3 + + receive_tone : 1, // Call Receive Tone: No or yes + _unused2 : 2; // 0b11 + + // Bytes 4-19 + uint16_t name[16]; // Contact Name (Unicode) +} contact_t; + +// +// Zone data. +// +typedef struct { + // Bytes 0-31 + uint16_t name[16]; // Zone Name (Unicode) + + // Bytes 32-63 + uint16_t member[16]; // Member: channels 1...16 +} zone_t; + +// +// Group list data. +// +typedef struct { + // Bytes 0-31 + uint16_t name[16]; // Group List Name (Unicode) + + // Bytes 32-95 + uint16_t member[32]; // Contacts +} grouplist_t; + +// +// Scan list data. +// +typedef struct { + // Bytes 0-31 + uint16_t name[16]; // Scan List Name (Unicode) + + // Bytes 32-37 + uint16_t priority_ch1; // Priority Channel 1 or ffff + uint16_t priority_ch2; // Priority Channel 2 or ffff + uint16_t tx_designated_ch; // Tx Designated Channel or ffff + + // Bytes 38-41 + uint8_t _unused1; // 0xf1 + uint8_t sign_hold_time; // Signaling Hold Time (x25 = msec) + uint8_t prio_sample_time; // Priority Sample Time (x250 = msec) + uint8_t _unused2; // 0xff + + // Bytes 42-103 + uint16_t member[31]; // Channels +} scanlist_t; + +// +// General settings. +// TODO: verify the general settings with official CPS +// +typedef struct { + + // Bytes 0-19 + uint16_t intro_line1[10]; + + // Bytes 20-39 + uint16_t intro_line2[10]; + + // Bytes 40-63 + uint8_t _unused40[24]; + + // Byte 64 + uint8_t _unused64_0 : 3, + monitor_type : 1, + _unused64_4 : 1, + disable_all_leds : 1, + _unused64_6 : 2; + + // Byte 65 + uint8_t talk_permit_tone : 2, + pw_and_lock_enable : 1, + ch_free_indication_tone : 1, + _unused65_4 : 1, + disable_all_tones : 1, + save_mode_receive : 1, + save_preamble : 1; + + // Byte 66 + uint8_t _unused66_0 : 2, + keypad_tones : 1, + intro_picture : 1, + _unused66_4 : 4; + + // Byte 67 + uint8_t _unused67; + + // Bytes 68-71 + uint8_t radio_id[3]; + uint8_t _unused71; + + // Bytes 72-84 + uint8_t tx_preamble_duration; + uint8_t group_call_hang_time; + uint8_t private_call_hang_time; + uint8_t vox_sensitivity; + uint8_t _unused76[2]; + uint8_t rx_low_battery_interval; + uint8_t call_alert_tone_duration; + uint8_t lone_worker_response_time; + uint8_t lone_worker_reminder_time; + uint8_t _unused82; + uint8_t scan_digital_hang_time; + uint8_t scan_analog_hang_time; + + // Byte 85 + uint8_t _unused85_0 : 6, + backlight_time : 2; + + // Bytes 86-87 + uint8_t set_keypad_lock_time; + uint8_t mode; + + // Bytes 88-95 + uint32_t power_on_password; + uint32_t radio_prog_password; + + // Bytes 96-103 + uint8_t pc_prog_password[8]; + + // Bytes 104-111 + uint8_t _unused104[8]; + + // Bytes 112-143 + uint16_t radio_name[16]; +} general_settings_t; + +static const char *POWER_NAME[] = { "Low", "High" }; +static const char *SQUELCH_NAME[] = { "Tight", "Normal" }; +static const char *BANDWIDTH[] = { "12.5", "20", "25", "25" }; +static const char *CONTACT_TYPE[] = { "-", "Group", "Private", "All" }; +static const char *ADMIT_NAME[] = { "-", "Free", "Tone", "Color" }; +static const char *INCALL_NAME[] = { "-", "Admit", "-", "Admit" }; + +#ifdef PRINT_RARE_PARAMS +static const char *REF_FREQUENCY[] = { "Low", "Med", "High" }; +static const char *PRIVACY_NAME[] = { "-", "Basic", "Enhanced" }; +static const char *SIGNALING_SYSTEM[] = { "-", "DTMF-1", "DTMF-2", "DTMF-3", "DTMF-4" }; +#endif + +// +// Print a generic information about the device. +// +static void rd5r_print_version(radio_device_t *radio, FILE *out) +{ + //TODO + 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]); + } +} + +// +// Read memory image from the device. +// +static void rd5r_download(radio_device_t *radio) +{ + //TODO +#if 0 + int bno; + + for (bno=0; bnoname, name, 16); +} + +// +// Add channel to a zone. +// Return 0 on failure. +// +static int zone_append(int index, int cnum) +{ + zone_t *z = GET_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) +{ + zone_t *z = GET_ZONE(index); + + memset(z, 0, 64); +} + +// +// Set parameters for a given scan list. +// +static void setup_scanlist(int index, const char *name, + int prio1, int prio2, int txchan) +{ + scanlist_t *sl = GET_SCANLIST(index); + + // Bytes 0-31 + utf8_decode(sl->name, name, 16); + + // Bytes 32-37 + sl->priority_ch1 = prio1; + sl->priority_ch2 = prio2; + sl->tx_designated_ch = txchan; +} + +static void erase_scanlist(int index) +{ + scanlist_t *sl = GET_SCANLIST(index); + + memset(sl, 0, 104); + + // Bytes 32-37 + sl->priority_ch1 = 0xffff; + sl->priority_ch2 = 0xffff; + sl->tx_designated_ch = 0xffff; + + // Bytes 38-41 + sl->_unused1 = 0xf1; + sl->sign_hold_time = 500 / 25; // 500 msec + sl->prio_sample_time = 2000 / 250; // 2 sec + sl->_unused2 = 0xff; +} + +// +// 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; + + for (i=0; i<31; i++) { + if (sl->member[i] == cnum) + return 1; + if (sl->member[i] == 0) { + sl->member[i] = cnum; + return 1; + } + } + return 0; +} + +static void erase_contact(int index) +{ + contact_t *ct = GET_CONTACT(index); + + memset(ct, 0, 36); + *(uint32_t*)ct = 0xffffffff; +} + +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; + ct->id[1] = id >> 8; + ct->id[2] = id >> 16; + ct->type = type; + ct->receive_tone = rxtone; + + utf8_decode(ct->name, name, 16); +} + +static void setup_grouplist(int index, const char *name) +{ + grouplist_t *gl = GET_GROUPLIST(index); + + utf8_decode(gl->name, name, 16); +} + +// +// Add contact to a grouplist. +// Return 0 on failure. +// +static int grouplist_append(int index, int cnum) +{ + grouplist_t *gl = GET_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; + return 1; + } + } + return 0; +} + +// +// Set text for a given message. +// +static void setup_message(int index, const char *text) +{ + uint16_t *msg = GET_MESSAGE(index); + + // Skip spaces and tabs. + while (*text == ' ' || *text == '\t') + text++; + utf8_decode(msg, text, 144); +} + +// +// 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; +} + +// +// 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 autoscan, int squelch, int tot, int rxonly, + int admit, int colorcode, int timeslot, int incall, int grouplist, int contact, + int rxtone, int txtone, int width) +{ + channel_t *ch = GET_CHANNEL(i); + + ch->channel_mode = mode; + ch->bandwidth = width; + ch->autoscan = autoscan; + ch->squelch = squelch; + ch->rx_only = rxonly; + ch->repeater_slot = timeslot; + ch->colorcode = colorcode; + ch->data_call_conf = 1; // Always ask for SMS acknowledge + ch->power = power; + ch->admit_criteria = admit; + ch->in_call_criteria = incall; + ch->contact_name_index = contact; + ch->tot = tot; + ch->scan_list_index = scanlist; + ch->group_list_index = grouplist; + ch->rx_frequency = mhz_to_bcd(rx_mhz); + ch->tx_frequency = mhz_to_bcd(tx_mhz); + ch->ctcss_dcs_receive = rxtone; + ch->ctcss_dcs_transmit = txtone; + + utf8_decode(ch->name, name, 16); +} + +// +// Erase the channel record. +// +static void erase_channel(int i) +{ + channel_t *ch = GET_CHANNEL(i); + + // Byte 0 + ch->channel_mode = MODE_ANALOG; + ch->bandwidth = BW_12_5_KHZ; + ch->autoscan = 0; + ch->squelch = SQ_NORMAL; + ch->_unused1 = 1; + ch->lone_worker = 0; + + // Byte 1 + ch->talkaround = 0; + ch->rx_only = 0; + ch->repeater_slot = 1; + ch->colorcode = 1; + + // Byte 2 + ch->privacy_no = 0; + ch->privacy = PRIV_NONE; + ch->private_call_conf = 0; + ch->data_call_conf = 0; + + // Byte 3 + ch->rx_ref_frequency = REF_LOW; + ch->_unused2 = 0; + ch->emergency_alarm_ack = 0; + ch->_unused3 = 2; + ch->uncompressed_udp = 1; + ch->display_pttid_dis = 1; + + // Byte 4 + ch->tx_ref_frequency = REF_LOW; + ch->_unused4 = 1; + ch->vox = 0; + ch->power = POWER_HIGH; + ch->admit_criteria = ADMIT_ALWAYS; + + // Byte 5 + ch->_unused5 = 0; + ch->in_call_criteria = INCALL_ALWAYS; + ch->_unused6 = 3; + + // Bytes 6-7 + ch->contact_name_index = 0; + + // Bytes 8-9 + ch->tot = 60/15; + ch->_unused13 = 0; + ch->tot_rekey_delay = 0; + + // Bytes 10-11 + ch->emergency_system_index = 0; + ch->scan_list_index = 0; + + // Bytes 12-13 + ch->group_list_index = 0; + ch->_unused7 = 0; + + // Bytes 14-15 + ch->_unused8 = 0; + ch->_unused9 = 0xff; + + // Bytes 16-23 + ch->rx_frequency = 0x40000000; + ch->tx_frequency = 0x40000000; + + // Bytes 24-27 + ch->ctcss_dcs_receive = 0xffff; + ch->ctcss_dcs_transmit = 0xffff; + + // Bytes 28-29 + ch->rx_signaling_syst = 0; + ch->tx_signaling_syst = 0; + + // Bytes 30-31 + ch->_unused10 = 0xff; + ch->_unused11 = 0xff; + + // Bytes 32-63 + utf8_decode(ch->name, "", 16); +} + +static void print_chanlist(FILE *out, uint16_t *unsorted, int nchan) +{ + int last = -1; + int range = 0; + int n; + uint16_t data[nchan]; + + // 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); + } + last = cnum; + } + if (range) + fprintf(out, "-%d", last); +} + +static void print_id(FILE *out, int verbose) +{ + general_settings_t *gs = GET_SETTINGS(); + unsigned id = gs->radio_id[0] | (gs->radio_id[1] << 8) | (gs->radio_id[2] << 16); + + 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_unicode(out, gs->radio_name, 16, 0); + } else { + fprintf(out, "-"); + } + fprintf(out, "\n"); +} + +static void print_intro(FILE *out, int verbose) +{ + general_settings_t *gs = GET_SETTINGS(); + + if (verbose) + fprintf(out, "\n# Text displayed when the radio powers up.\n"); + fprintf(out, "Intro Line 1: "); + if (VALID_TEXT(gs->intro_line1)) { + print_unicode(out, gs->intro_line1, 10, 0); + } else { + fprintf(out, "-"); + } + fprintf(out, "\nIntro Line 2: "); + if (VALID_TEXT(gs->intro_line2)) { + print_unicode(out, gs->intro_line2, 10, 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 +// Autoscan +// TOT +// RX Only +// Admit Criteria +// +static void print_chan_base(FILE *out, channel_t *ch, int cnum) +{ + fprintf(out, "%5d ", cnum); + print_unicode(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); + + fprintf(out, "%c ", "-+"[ch->autoscan]); + + if (ch->tot == 0) + fprintf(out, "- "); + else + fprintf(out, "%-3d ", ch->tot * 15); + + fprintf(out, "%c ", "-+"[ch->rx_only]); + + fprintf(out, "%-6s ", ADMIT_NAME[ch->admit_criteria]); +} + +#ifdef PRINT_RARE_PARAMS +// +// Print extended parameters of the channel: +// TOT Rekey Delay +// RX Ref Frequency +// RX Ref Frequency +// Lone Worker +// VOX +// +static void print_chan_ext(FILE *out, channel_t *ch) +{ + fprintf(out, "%-3d ", ch->tot_rekey_delay); + fprintf(out, "%-5s ", REF_FREQUENCY[ch->rx_ref_frequency]); + fprintf(out, "%-5s ", REF_FREQUENCY[ch->tx_ref_frequency]); + fprintf(out, "%c ", "-+"[ch->lone_worker]); + 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) Autoscan flag: -, +\n"); + fprintf(out, "# 8) Transmit timeout timer in seconds: 0, 15, 30, 45... 555\n"); + fprintf(out, "# 9) Receive only: -, +\n"); + fprintf(out, "# 10) Admit criteria: -, Free, Color\n"); + fprintf(out, "# 11) Color code: 0, 1, 2, 3... 15\n"); + fprintf(out, "# 12) Time slot: 1 or 2\n"); + fprintf(out, "# 13) In call criteria: -, Admit, TXInt\n"); + fprintf(out, "# 14) Receive group list: - or index in Grouplist table\n"); + fprintf(out, "# 15) Contact for transmit: - or index in Contacts table\n"); + fprintf(out, "#\n"); + } + fprintf(out, "Digital Name Receive Transmit Power Scan AS TOT RO Admit Color Slot InCall RxGL TxContact"); +#ifdef PRINT_RARE_PARAMS + fprintf(out, " Dly RxRef TxRef LW VOX TA EmSys Privacy PN PCC EAA DCC CU"); +#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 + // In Call Criteria + // Group List + // Contact Name + fprintf(out, "%-5d %-3d ", ch->colorcode, ch->repeater_slot); + fprintf(out, "%-6s ", INCALL_NAME[ch->in_call_criteria]); + + 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 + print_chan_ext(out, ch); + + // Extended digital parameters of the channel: + // Emergency System + // Privacy + // Privacy No. (+1) + // 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 == PRIV_NONE) + fprintf(out, "- "); + else + fprintf(out, "%-2d ", ch->privacy_no + 1); + + fprintf(out, "%c ", "-+"[ch->private_call_conf]); + fprintf(out, "%c ", "-+"[ch->emergency_alarm_ack]); + fprintf(out, "%c ", "-+"[ch->data_call_conf]); + fprintf(out, "%c ", "+-"[ch->uncompressed_udp]); +#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_unicode(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) Autoscan flag: -, +\n"); + fprintf(out, "# 8) Transmit timeout timer in seconds: 0, 15, 30, 45... 555\n"); + fprintf(out, "# 9) Receive only: -, +\n"); + fprintf(out, "# 10) Admit criteria: -, Free, Tone\n"); + fprintf(out, "# 11) Squelch level: Normal, Tight\n"); + fprintf(out, "# 12) Guard tone for receive, or '-' to disable\n"); + fprintf(out, "# 13) Guard tone for transmit, or '-' to disable\n"); + fprintf(out, "# 14) Bandwidth in kHz: 12.5, 20, 25\n"); + fprintf(out, "#\n"); + } + fprintf(out, "Analog Name Receive Transmit Power Scan AS TOT RO Admit Squelch RxTone TxTone Width"); +#ifdef PRINT_RARE_PARAMS + fprintf(out, " Dly RxRef TxRef LW VOX TA RxSign TxSign ID"); +#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, " %s", BANDWIDTH[ch->bandwidth]); + +#ifdef PRINT_RARE_PARAMS + print_chan_ext(out, ch); + + // Extended analog parameters of the channel: + // Rx Signaling System + // Tx Signaling System + // Display PTT ID (inverted) + // Non-QT/DQT Turn-off Freq. + fprintf(out, "%-6s ", SIGNALING_SYSTEM[ch->rx_signaling_syst]); + fprintf(out, "%-6s ", SIGNALING_SYSTEM[ch->tx_signaling_syst]); + fprintf(out, "%c ", "+-"[ch->display_pttid_dis]); +#endif + fprintf(out, "\n"); + } +} + +static int have_zones() +{ + int i; + + for (i=0; iname); + if (verbose) + rd5r_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); + } 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, 16, 1); + if (sl->priority_ch1 == 0xffff) { + fprintf(out, " - "); + } else if (sl->priority_ch1 == 0) { + fprintf(out, " Sel "); + } else { + fprintf(out, " %-4d ", sl->priority_ch1); + } + if (sl->priority_ch2 == 0xffff) { + fprintf(out, "- "); + } else if (sl->priority_ch2 == 0) { + fprintf(out, "Sel "); + } else { + fprintf(out, "%-4d ", sl->priority_ch2); + } + if (sl->tx_designated_ch == 0xffff) { + fprintf(out, "Last "); + } else if (sl->tx_designated_ch == 0) { + fprintf(out, "Sel "); + } else { + fprintf(out, "%-4d ", sl->tx_designated_ch); + } +#ifdef PRINT_RARE_PARAMS + fprintf(out, "%-4d %-4d ", + sl->sign_hold_time * 25, sl->prio_sample_time * 250); +#endif + if (sl->member[0]) { + print_chanlist(out, sl->member, 31); + } else { + fprintf(out, "-"); + } + 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); + } else { + fprintf(out, "-"); + } + fprintf(out, "\n"); + } + } + + // + // Text messages. + // + if (have_messages()) { + 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; iradio_name, value, 16); + return; + } + if (strcasecmp ("ID", param) == 0) { + uint32_t id = strtoul(value, 0, 0); + gs->radio_id[0] = id; + gs->radio_id[1] = id >> 8; + gs->radio_id[2] = id >> 16; + return; + } + if (strcasecmp ("Last Programmed Date", param) == 0) { + // Ignore. + return; + } + if (strcasecmp ("CPS Software Version", param) == 0) { + // Ignore. + return; + } + if (strcasecmp ("Intro Line 1", param) == 0) { + utf8_decode(gs->intro_line1, value, 10); + return; + } + if (strcasecmp ("Intro Line 2", param) == 0) { + utf8_decode(gs->intro_line2, value, 10); + 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], autoscan_str[256]; + char tot_str[256], rxonly_str[256], admit_str[256], colorcode_str[256]; + char slot_str[256], incall_str[256], grouplist_str[256], contact_str[256]; + int num, power, scanlist, autoscan, tot, rxonly, admit; + int colorcode, timeslot, incall, grouplist, contact; + double rx_mhz, tx_mhz; + + if (sscanf(line, "%s %s %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, autoscan_str, + tot_str, rxonly_str, admit_str, colorcode_str, + slot_str, incall_str, grouplist_str, contact_str) != 15) + 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 (*autoscan_str == '-') { + autoscan = 0; + } else if (*autoscan_str == '+') { + autoscan = 1; + } else { + fprintf(stderr, "Bad autoscan flag.\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 (*incall_str == '-' || strcasecmp("Always", incall_str) == 0) { + incall = INCALL_ALWAYS; + } else if (strcasecmp("Admit", incall_str) == 0) { + incall = INCALL_ADMIT; + } else { + fprintf(stderr, "Bad incall criteria.\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, autoscan, SQ_NORMAL, tot, rxonly, admit, + colorcode, timeslot, incall, 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], autoscan_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, autoscan, 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 %s", + num_str, name_str, rxfreq_str, offset_str, + power_str, scanlist_str, autoscan_str, + tot_str, rxonly_str, admit_str, squelch_str, + rxtone_str, txtone_str, width_str) != 14) + 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 (*autoscan_str == '-') { + autoscan = 0; + } else if (*autoscan_str == '+') { + autoscan = 1; + } else { + fprintf(stderr, "Bad autoscan flag.\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 if (strcasecmp("Tone", admit_str) == 0) { + admit = ADMIT_TONE; + } 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 ("20", width_str) == 0) { + width = BW_20_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(); + } + + setup_channel(num-1, MODE_ANALOG, name_str, rx_mhz, tx_mhz, + power, scanlist, autoscan, squelch, tot, rxonly, admit, + 1, 1, 0, 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 = 0xffff; + } else if (strcasecmp("Sel", prio1_str) == 0) { + prio1 = 0; + } else { + prio1 = atoi(prio1_str); + if (prio1 < 1 || prio1 > NCHAN) { + fprintf(stderr, "Bad priority channel 1.\n"); + return 0; + } + } + + if (*prio2_str == '-') { + prio2 = 0xffff; + } else if (strcasecmp("Sel", prio2_str) == 0) { + prio2 = 0; + } else { + prio2 = atoi(prio2_str); + if (prio2 < 1 || prio2 > NCHAN) { + fprintf(stderr, "Bad priority channel 2.\n"); + return 0; + } + } + + if (strcasecmp("Last", tx_str) == 0) { + txchan = 0xffff; + } else if (strcasecmp("Sel", tx_str) == 0) { + txchan = 0; + } else { + txchan = atoi(tx_str); + if (txchan < 1 || txchan > NCHAN) { + fprintf(stderr, "Bad transmit channel.\n"); + return 0; + } + } + + setup_scanlist(snum-1, name_str, prio1, prio2, txchan); + + 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, "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_GLISTS], 0, NGLISTS*96); + } + + 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(&radio_mem[OFFSET_MSG], 0, NMESSAGES*288); + } + + setup_message(mnum-1, text); + return 1; +} + +// +// Parse table header. +// Return table id, or 0 in case of error. +// +static int rd5r_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 rd5r_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 rd5r_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 + 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; + } + } +} + +// +// Check that configuration is correct. +// Return 0 on error. +// +static int rd5r_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 (!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[k]; + + if (cnum != 0) { + channel_t *ch = GET_CHANNEL(cnum - 1); + + if (!VALID_CHANNEL(ch)) { + fprintf(stderr, "Zone %d '", 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); + return 1; +} + +// +// Baofeng RD-5R +// +radio_device_t radio_rd5r = { + "Baofeng RD-5R", + rd5r_download, + rd5r_upload, + rd5r_is_compatible, + rd5r_read_image, + rd5r_save_image, + rd5r_print_version, + rd5r_print_config, + rd5r_verify_config, + rd5r_parse_parameter, + rd5r_parse_header, + rd5r_parse_row, + rd5r_update_timestamp, +};