diff --git a/Makefile b/Makefile index 1cf19a2..f51f19d 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,8 @@ VERSION = $(shell git describe --tags --abbrev=0) GITCOUNT = $(shell git rev-list HEAD --count) UNAME = $(shell uname) -OBJS = main.o util.o radio.o dfu-libusb.o uv380.o md380.o rd5r.o gd77.o hid.o serial.o d868uv.o +OBJS = main.o util.o radio.o dfu-libusb.o uv380.o md380.o rd5r.o \ + gd77.o hid.o serial.o d868uv.o dm1801.o LDFLAGS = -g LIBS = $(shell pkg-config --libs --static libusb-1.0) CFLAGS = -g -O -Wall -Werror -DVERSION='"$(VERSION).$(GITCOUNT)"' \ diff --git a/Makefile-mingw b/Makefile-mingw index 618965c..2233756 100644 --- a/Makefile-mingw +++ b/Makefile-mingw @@ -1,11 +1,12 @@ -CC = gcc +CC = gcc 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 +CFLAGS = -g -O -Wall -Werror -DVERSION='"$(VERSION).$(GITCOUNT)"' +LDFLAGS = -g -s -OBJS = main.o util.o radio.o dfu-windows.o uv380.o md380.o rd5r.o gd77.o hid.o hid-windows.o serial.o d868uv.o +OBJS = main.o util.o radio.o dfu-windows.o uv380.o md380.o rd5r.o \ + gd77.o hid.o hid-windows.o serial.o d868uv.o dm1801.o LIBS = -lhid -lsetupapi # Compiling Windows binary from Linux @@ -21,15 +22,15 @@ ifeq (/usr/local/bin/i686-w64-mingw32-gcc,$(wildcard /usr/local/bin/i686-w64-min CC = i686-w64-mingw32-gcc endif -all: dmrconfig.exe +all: dmrconfig.exe -dmrconfig.exe: $(OBJS) +dmrconfig.exe: $(OBJS) $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) clean: rm -f *~ *.o core dmrconfig -install: dmrconfig +install: dmrconfig install -c -s dmrconfig /usr/local/bin/dmrconfig ### diff --git a/dm1801.c b/dm1801.c new file mode 100644 index 0000000..e73de3e --- /dev/null +++ b/dm1801.c @@ -0,0 +1,2345 @@ +/* + * Interface to Baofeng DM-1801. + * + * Copyright (C) 2019 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 dm1801_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); +} + +// +// Read memory image. +// Same as Radioddity GD-77 new firmware. +// +static void dm1801_download(radio_device_t *radio) +{ + download(radio); + + // Add header. + memcpy(&radio_mem[0], "MD-760P", 7); +} + +// +// Write memory image to the device. +// +static void dm1801_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 dm1801_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 +// 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, "%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 + 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 RxRef TxRef LW 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, " %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 + 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 dm1801_print_config(radio_device_t *radio, FILE *out, int verbose) +{ + int i; + + fprintf(out, "Radio: %s\n", radio->name); + if (verbose) + dm1801_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 dm1801_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 dm1801_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 dm1801_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 dm1801_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 dm1801_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 dm1801_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; +} + +// +// Baofeng DM-1801 +// +radio_device_t radio_dm1801 = { + "Baofeng DM-1801", + dm1801_download, + dm1801_upload, + dm1801_is_compatible, + dm1801_read_image, + dm1801_save_image, + dm1801_print_version, + dm1801_print_config, + dm1801_verify_config, + dm1801_parse_parameter, + dm1801_parse_header, + dm1801_parse_row, + dm1801_update_timestamp, + //TODO: dm1801_write_csv, +}; diff --git a/gd77.c b/gd77.c index 9acb0ed..4f5e91a 100644 --- a/gd77.c +++ b/gd77.c @@ -2342,23 +2342,3 @@ radio_device_t radio_gd77 = { gd77_update_timestamp, //TODO: gd77_write_csv, }; - -// -// Baofeng DM-1801 -// -radio_device_t radio_dm1801 = { - "Baofeng DM-1801", - 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, -}; diff --git a/radio.c b/radio.c index 2c8a681..c5a7581 100644 --- a/radio.c +++ b/radio.c @@ -46,9 +46,9 @@ static struct { { "2017", &radio_md2017 }, // TYT MD-2017, Retevis RT82 { "MD9600", &radio_md9600 }, // TYT MD-9600 { "BF-5R", &radio_rd5r }, // Baofeng RD-5R, TD-5R + { "1801", &radio_dm1801 }, // Baofeng DM-1801 { "DM-1701", &radio_rt84 }, // Baofeng DM-1701, Retevis RT84 { "MD-760P", &radio_gd77 }, // Radioddity GD-77, version 3.1.1 and later - { "1801", &radio_dm1801 }, // Baofeng DM-1801 { "D868UVE", &radio_d868uv }, // Anytone AT-D868UV { "D878UV", &radio_d878uv }, // Anytone AT-D878UV { "D6X2UV", &radio_dmr6x2 }, // BTECH DMR-6x2