diff --git a/LICENSE b/LICENSE index 261eeb9..10e4919 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,29 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +BSD 3-Clause License - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +Copyright (C) 2018-2019, Serge Vakulenko KK6ABQ +All rights reserved. - 1. Definitions. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. +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. - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. diff --git a/Makefile b/Makefile index be8a605..c039939 100644 --- a/Makefile +++ b/Makefile @@ -1,27 +1,36 @@ -CC ?= gcc +CC ?= gcc VERSION = $(shell git describe --tags --abbrev=0) GITCOUNT = $(shell git rev-list HEAD --count) UNAME = $(shell uname) -CFLAGS ?= -g -O -Wall -Werror -CFLAGS += -DVERSION='"$(VERSION).$(GITCOUNT)"' -LDFLAGS ?= -g -OBJS = main.o util.o radio.o dfu-libusb.o uv380.o md380.o rd5r.o gd77.o hid.o serial.o d868uv.o -LIBS = -lusb-1.0 +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 +CFLAGS ?= -g -O -Wall -Werror +CFLAGS += -DVERSION='"$(VERSION).$(GITCOUNT)"' \ + $(shell pkg-config --cflags libusb-1.0) +LDFLAGS ?= -g +LIBS = $(shell pkg-config --libs --static libusb-1.0) + +# +# Make sure pkg-config is installed. +# +ifeq ($(shell pkg-config --version),) + $(error Fatal error: pkg-config is not installed) +endif # # Linux # # To install required libraries, use: -# sudo apt install libusb-1.0-0-dev libudev-dev +# sudo apt-get install pkg-config libusb-1.0-0-dev libudev-dev # ifeq ($(UNAME),Linux) OBJS += hid-libusb.o - LIBS += -ludev + + # Link libusb statically, when possible LIBUSB = /usr/lib/x86_64-linux-gnu/libusb-1.0.a ifeq ($(wildcard $(LIBUSB)),$(LIBUSB)) - # Link libusb statically, when possible LIBS = $(LIBUSB) -lpthread -ludev endif endif @@ -29,6 +38,9 @@ endif # # Mac OS X # +# To install required libraries, use: +# brew install pkg-config libusb +# ifeq ($(UNAME),Darwin) OBJS += hid-macos.o LIBS += -framework IOKit -framework CoreFoundation @@ -45,10 +57,6 @@ clean: install: dmrconfig install -c -s dmrconfig /usr/local/bin/dmrconfig -dmrconfig.linux: dmrconfig - cp -p $< $@ - strip $@ - ### d868uv.o: d868uv.c radio.h util.h d868uv-map.h dfu-libusb.o: dfu-libusb.c util.h 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/README.md b/README.md index 40a5236..9b33680 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Supported radios: * TYT MD-9600 * Baofeng DM-1701, Retevis RT84 * Baofeng RD-5R, TD-5R + * Baofeng DM-1801 * Radioddity GD-77 * Anytone AT-D868UV * Anytone AT-D878UV @@ -59,7 +60,7 @@ Create a file /etc/udev/rules.d/99-dmr.rules with the following contents: # TYT MD-UV380 SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", MODE="666" - # Baofeng RD-5R, TD-5R + # Baofeng RD-5R, TD-5R, DM-1801 SUBSYSTEM=="usb", ATTRS{idVendor}=="15a2", ATTRS{idProduct}=="0073", MODE="666" # Anytone AT-D868UV: ignore this device in Modem Manager diff --git a/d868uv.c b/d868uv.c index 320630c..8f3ecc5 100644 --- a/d868uv.c +++ b/d868uv.c @@ -1595,6 +1595,12 @@ static void setup_channel(radio_device_t *radio, int i, int mode, char *name, } } + if (ch->rx_ctcss == 0 && ch->rx_dcs == 0) { + ch->squelch_mode = SQ_CARRIER; + } else { + ch->squelch_mode = SQ_TONE; + } + if (txtone > 0) { // Transmit DCS ch->tx_dcs = 1; ch->dcs_transmit = txtone - 1; @@ -2857,8 +2863,9 @@ static void d868uv_write_csv(radio_device_t *radio, FILE *csv) // Add map record. if (sz.count >= NCALLSIGNS) { - fprintf(stderr, "Too many contacts!\n"); - return; + fprintf(stderr, "WARNING: Too many callsigns!\n"); + fprintf(stderr, "Skipping the rest.\n"); + break; } callsign_map_t *m = &map[sz.count]; sz.count++; diff --git a/dfu-libusb.c b/dfu-libusb.c index 5281a3f..8b4c760 100644 --- a/dfu-libusb.c +++ b/dfu-libusb.c @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include "util.h" // diff --git a/dm1801.c b/dm1801.c new file mode 100644 index 0000000..ec2fd75 --- /dev/null +++ b/dm1801.c @@ -0,0 +1,2346 @@ +/* + * 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 150 +#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-79 + uint16_t member[32]; // Member: channels 1...32 +} 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...0x1ee5f. +#define NBLK 989 + for (bno = 1; bno < NBLK; 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[0x1ee60], 0xff, MEMSZ - 0x1ee60); + 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], "1801", 4); +} + +// +// Write memory image to the device. +// +static void dm1801_upload(radio_device_t *radio, int cont_flag) +{ + int bno; + + // Write range 0x80...0x1ee5f. + for (bno = 1; bno < NBLK; 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("1801", (char*)&radio_mem[0], 4) == 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<32; 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, 32, 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/examples/dm1801-south-bay-area.conf b/examples/dm1801-south-bay-area.conf new file mode 100644 index 0000000..68b6584 --- /dev/null +++ b/examples/dm1801-south-bay-area.conf @@ -0,0 +1,369 @@ +# +# Generic DMR configuration for South Bay Area. +# +Radio: Baofeng DM-1801 + +# Table of analog channels. +# 1) Channel number: 1-1024 +# 2) Name: up to 16 characters, use '_' instead of space +# 3) Receive frequency in MHz +# 4) Transmit frequency or +/- offset in MHz +# 5) Transmit power: High, Low +# 6) Scan list: - or index +# 7) Transmit timeout timer in seconds: 0, 15, 30, 45... 555 +# 8) Receive only: -, + +# 9) Admit criteria: -, Free, Tone +# 10) Squelch level: Normal, Tight +# 11) Guard tone for receive, or '-' to disable +# 12) Guard tone for transmit, or '-' to disable +# 13) Bandwidth in kHz: 12.5, 20, 25 +# +Analog Name Receive Transmit Power Scan TOT RO Admit Squelch RxTone TxTone Width + +# VHF Band 144-148 + 201 Palo_Alto_N6NFI 145.230 -0.6 High 1 180 - Free Normal - 100.0 25 + 202 Milpitas_W6MLP 145.430 -0.6 High 1 180 - Free Normal - 85.4 25 + 203 Saratoga_K6SA 146.655 -0.6 High 1 180 - Free Normal - 114.8 25 + 204 Newark_N6MM 146.670 -0.6 High 1 180 - Free Normal - 110.9 25 + 205 Mill_Vally_K6GWE 146.700 -0.6 High 1 180 - Free Normal - 179.9 25 + 206 San_Jose_K6INC 146.820 -0.6 High 1 180 - Free Normal - 123.0 25 + 207 Orinda_K6LNK 146.850 -0.6 High 1 180 - Free Normal - 103.5 25 + 208 Pleasanton_W6SRR 147.045 +0.6 High 1 180 - Free Normal - 94.8 25 + 209 Concord_W6CX 147.060 +0.6 High 1 180 - Free Normal - 100.0 25 + 210 Napa_W6CO 147.180 +0.6 High 1 180 - Free Normal - 91.5 25 + 211 Morgan_Hil_K7DAA 147.330 +0.6 High 1 180 - Free Normal - 103.5 25 + 212 Los_Gatos_AB6LI 147.945 -0.6 High 1 180 - Free Normal - 156.7 25 + +# UHF Band 440-446 + 301 San_Jose_W6YOP 440.275 +5 High 1 180 - Free Normal - 127.3 25 + 302 Woodside_N6ZX 440.450 +5 High 1 180 - Free Normal - 107.2 25 + 303 Campbell_NO1PC 441.025 +5 High 1 180 - Free Normal - 110.9 25 + 304 Los_Gatos_K6UB 441.700 +5 High 1 180 - Free Normal - 127.3 25 + 305 San_Jose_WB6ZVW 442.500 +5 High 1 180 - Free Normal - 100.0 25 + 306 San_Jose_WR6ABD 442.900 +5 High 1 180 - Free Normal - 162.2 25 + 307 Morgan_Hil_K7DAA 442.975 +5 High 1 180 - Free Normal - 100.0 25 + 308 Orinda_N6QOP 443.050 +5 High 1 180 - Free Normal - 114.8 25 + 309 San_Jose_K6LNK 443.075 +5 High 1 180 - Free Normal - 123.0 25 + 310 Palo_Alto_WW6BAY 443.225 +5 High 1 180 - Free Normal - 100.0 25 + 311 San_Jose_KE6STH 443.575 +5 High 1 180 - Free Normal - 110.9 25 + 312 Oakland_WW6BAY 443.975 +5 High 1 180 - Free Normal - 100.0 25 + 313 San_Jose_K6GOD 444.725 +5 High 1 180 - Free Normal - 162.2 25 + 314 Los_Gatos_WB6KHP 444.975 +5 High 1 180 - Free Normal - 127.3 25 + +# Simplex frequencies + 401 S_446.0 446.000 +0 High 2 180 - Free Normal - - 25 + 402 S_446.5 446.500 +0 High 2 180 - Free Normal - - 25 + 403 S_441.0 441.000 +0 High 2 180 - Free Normal - - 25 + 404 S_146.52 146.520 +0 High 2 180 - Free Normal - - 25 + 405 S_146.46 146.460 +0 High 2 180 - Free Normal - - 25 + 406 S_147.42 147.420 +0 High 2 180 - Free Normal - - 25 + 407 WX_1 162.450 +0 Low - 180 + Free Normal - - 25 + 408 WX_5 162.550 +0 Low - 180 + Free Normal - - 25 + +# Table of digital channels. +# 1) Channel number: 1-1024 +# 2) Name: up to 16 characters, use '_' instead of space +# 3) Receive frequency in MHz +# 4) Transmit frequency or +/- offset in MHz +# 5) Transmit power: High, Low +# 6) Scan list: - or index in Scanlist table +# 7) Transmit timeout timer in seconds: 0, 15, 30, 45... 555 +# 8) Receive only: -, + +# 9) Admit criteria: -, Free, Color +# 10) Color code: 0, 1, 2, 3... 15 +# 11) Time slot: 1 or 2 +# 12) Receive group list: - or index in Grouplist table +# 13) Contact for transmit: - or index in Contacts table +# +Digital Name Receive Transmit Power Scan TOT RO Admit Color Slot RxGL TxContact + +# (1) W6TCP, Milpitas, NorCal + 1 Mi_RX_All 440.125 +5 Low - 180 + Color 3 1 1 - # Receive only + 2 Mi_World 440.125 +5 High - 180 - Color 3 1 11 91 # 91 Worldwide + 3 Mi_North_America 440.125 +5 High - 180 - Color 3 1 12 93 # 93 North America + 4 Mi_TAC_310 440.125 +5 High - 180 - Color 3 1 13 10 # 310 TAC 310 + 5 Mi_TAC_311 440.125 +5 High - 180 - Color 3 1 14 11 # 311 TAC 311 + 6 Mi_USA_Nation 440.125 +5 High - 180 - Color 3 1 15 100 # 3100 USA Nationwide + 7 Mi_California 440.125 +5 High - 180 - Color 3 1 16 106 # 3106 California + 8 Mi_NorCal 440.125 +5 High - 180 - Color 3 2 19 68 # 31068 NorCal + 9 Mi_Bay-Net 440.125 +5 High - 180 - Color 3 1 20 75 # 31075 Bay-Net + 10 Mi_Anarchy 440.125 +5 High - 180 - Color 3 2 23 66 # 31666 DMR of Anarchy + 11 Mi_NC_5150 440.125 +5 High - 180 - Color 3 2 24 95 # 95150 NorCal 5150 + 12 Mi_Parrot 440.125 +5 High - 180 - Color 3 1 - 90 # 9990 Parrot + +# (2) N6AMG, San Bruno, Baycom + 21 Br_RX_All 440.500 +5 Low - 180 + Color 1 1 1 - # Receive only + 22 Br_World 440.500 +5 High - 180 - Color 1 1 11 91 # 91 Worldwide + 23 Br_North_America 440.500 +5 High - 180 - Color 1 1 12 93 # 93 North America + 24 Br_TAC_310 440.500 +5 High - 180 - Color 1 1 13 10 # 310 TAC 310 + 25 Br_TAC_311 440.500 +5 High - 180 - Color 1 1 14 11 # 311 TAC 311 + 26 Br_USA_Nation 440.500 +5 High - 180 - Color 1 1 15 100 # 3100 USA Nationwide + 27 Br_California 440.500 +5 High - 180 - Color 1 1 16 106 # 3106 California + 28 Br_NC_AllStr 440.500 +5 High - 180 - Color 1 1 18 65 # 31065 NorCal AllStar + 29 Br_NorCal 440.500 +5 High - 180 - Color 1 2 19 68 # 31068 NorCal + 30 Br_Bay-Net 440.500 +5 High - 180 - Color 1 1 20 75 # 31075 Bay-Net + 31 Br_USA_Area6 440.500 +5 High - 180 - Color 1 1 22 96 # 31096 USA Area 6 + 32 Br_Anarchy 440.500 +5 High - 180 - Color 1 2 23 66 # 31666 DMR of Anarchy + 33 Br_NC_5150 440.500 +5 High - 180 - Color 1 2 24 95 # 95150 NorCal 5150 + 34 Br_Baycom 440.500 +5 High - 180 - Color 1 2 25 97 # 97150 Baycom + 35 Br_Parrot 440.500 +5 High - 180 - Color 1 1 - 90 # 9990 Parrot + +# (3) WB6ECE, Boulder Creek, Baycom + 41 Bo_RX_All 440.5875 +5 Low - 180 + Color 2 1 1 - # Receive only + 42 Bo_World 440.5875 +5 High - 180 - Color 2 1 11 91 # 91 Worldwide + 43 Bo_North_America 440.5875 +5 High - 180 - Color 2 1 12 93 # 93 North America + 44 Bo_TAC_310 440.5875 +5 High - 180 - Color 2 1 13 10 # 310 TAC 310 + 45 Bo_TAC_311 440.5875 +5 High - 180 - Color 2 1 14 11 # 311 TAC 311 + 46 Bo_USA_Nation 440.5875 +5 High - 180 - Color 2 1 15 100 # 3100 USA Nationwide + 47 Bo_California 440.5875 +5 High - 180 - Color 2 1 16 106 # 3106 California + 48 Bo_Santa_Clara 440.5875 +5 High - 180 - Color 2 1 17 64 # 31064 Santa Clara County + 49 Bo_NC_AllStr 440.5875 +5 High - 180 - Color 2 1 18 65 # 31065 NorCal AllStar + 50 Bo_NorCal 440.5875 +5 High - 180 - Color 2 2 19 68 # 31068 NorCal + 51 Bo_Bay-Net 440.5875 +5 High - 180 - Color 2 1 20 75 # 31075 Bay-Net + 52 Bo_USA_Area6 440.5875 +5 High - 180 - Color 2 1 22 96 # 31096 USA Area 6 + 53 Bo_Anarchy 440.5875 +5 High - 180 - Color 2 2 23 66 # 31666 DMR of Anarchy + 54 Bo_NC_5150 440.5875 +5 High - 180 - Color 2 2 24 95 # 95150 NorCal 5150 + 55 Bo_Baycom 440.5875 +5 High - 180 - Color 2 2 25 97 # 97150 Baycom + 56 Bo_Parrot 440.5875 +5 High - 180 - Color 2 1 - 90 # 9990 Parrot + +# (4) K6OTR, Palo Alto, Baycom + 61 PA_RX_All 441.850 +5 Low - 180 + Color 1 1 1 - # Receive only + 62 PA_World 441.850 +5 High - 180 - Color 1 1 11 91 # 91 Worldwide + 63 PA_North_America 441.850 +5 High - 180 - Color 1 1 12 93 # 93 North America + 64 PA_TAC_310 441.850 +5 High - 180 - Color 1 1 13 10 # 310 TAC 310 + 65 PA_TAC_311 441.850 +5 High - 180 - Color 1 1 14 11 # 311 TAC 311 + 66 USA_Nation 441.850 +5 High - 180 - Color 1 1 15 100 # 3100 USA Nationwide + 67 PA_California 441.850 +5 High - 180 - Color 1 1 16 106 # 3106 California + 68 PA_NC_AllStr 441.850 +5 High - 180 - Color 1 1 18 65 # 31065 NorCal AllStar + 69 PA_NorCal 441.850 +5 High - 180 - Color 1 2 19 68 # 31068 NorCal + 70 PA_Bay-Net 441.850 +5 High - 180 - Color 1 1 20 75 # 31075 Bay-Net + 71 PA_USA_Area6 441.850 +5 High - 180 - Color 1 1 22 96 # 31096 USA - Area 6 4646 + 72 PA_Anarchy 441.850 +5 High - 180 - Color 1 2 23 66 # 31666 DMR of Anarchy + 73 PA_NC_5150 441.850 +5 High - 180 - Color 1 2 24 95 # 95150 NorCal 5150 + 74 PA_Baycom 441.850 +5 High - 180 - Color 1 2 25 97 # 97150 Baycom + 75 PA_Parrot 441.850 +5 High - 180 - Color 1 1 - 90 # 9990 Parrot + +# (5) KK6USZ, Saratoga + 81 Sa_RX_All 441.950 +5 Low - 180 + Color 1 1 1 - # Receive only + 82 Sa_World 441.950 +5 High - 180 - Color 1 1 11 91 # 91 Worldwide + 83 Sa_North_America 441.950 +5 High - 180 - Color 1 1 12 93 # 93 North America + 84 Sa_TAC_310 441.950 +5 High - 180 - Color 1 1 13 10 # 310 TAC 310 + 85 Sa_TAC_311 441.950 +5 High - 180 - Color 1 1 14 11 # 311 TAC 311 + 86 Sa_USA_Nation 441.950 +5 High - 180 - Color 1 1 15 100 # 3100 USA Nationwide + 87 Sa_California 441.950 +5 High - 180 - Color 1 1 16 106 # 3106 California + 88 Sa_Nebraska 441.950 +5 High - 180 - Color 1 1 26 131 # 3131 Nebraska + 89 Sa_Texas 441.950 +5 High - 180 - Color 1 1 27 148 # 3148 Texas + 90 Sa_Santa_Clara 441.950 +5 High - 180 - Color 1 2 17 64 # 31064 Santa Clara County + 91 Sa_NorCal 441.950 +5 High - 180 - Color 1 2 19 68 # 31068 NorCal + 92 Sa_Bay-Net 441.950 +5 High - 180 - Color 1 2 20 75 # 31075 Bay-Net + 93 Sa_Anarchy 441.950 +5 High - 180 - Color 1 2 23 66 # 31666 DMR of Anarchy + 94 Sa_NC_5150 441.950 +5 High - 180 - Color 1 2 24 95 # 95150 NorCal 5150 + 95 Sa_Baycom 441.950 +5 High - 180 - Color 1 2 25 97 # 97150 Baycom + 96 Sa_Parrot 441.950 +5 High - 180 - Color 1 1 - 90 # 9990 Parrot + +# (6) WA6YCZ, Mt Umunhum, Baycom + 101 Um_RX_All 442.5375 +5 Low - 180 + Color 1 1 1 - # Receive only + 102 Um_World 442.5375 +5 High - 180 - Color 1 1 11 91 # 91 Worldwide + 103 Um_North_America 442.5375 +5 High - 180 - Color 1 1 12 93 # 93 North America + 104 Um_TAC_310 442.5375 +5 High - 180 - Color 1 1 13 10 # 310 TAC 310 + 105 Um_TAC_311 442.5375 +5 High - 180 - Color 1 1 14 11 # 311 TAC 311 + 106 Um_USA_Nation 442.5375 +5 High - 180 - Color 1 1 15 100 # 3100 USA Nationwide + 107 Um_California 442.5375 +5 High - 180 - Color 1 1 16 106 # 3106 California + 108 Um_Santa_Clara 442.5375 +5 High - 180 - Color 1 1 17 64 # 31064 Santa Clara County + 109 Um_NC_AllStr 442.5375 +5 High - 180 - Color 1 1 18 65 # 31065 NorCal AllStar + 110 Um_NorCal 442.5375 +5 High - 180 - Color 1 2 19 68 # 31068 NorCal + 111 Um_Bay-Net 442.5375 +5 High - 180 - Color 1 1 20 75 # 31075 Bay-Net + 112 Um_USA_Area6 442.5375 +5 High - 180 - Color 1 1 22 96 # 31096 USA Area 6 + 113 Um_Anarchy 442.5375 +5 High - 180 - Color 1 2 23 66 # 31666 DMR of Anarchy + 114 Um_NC_5150 442.5375 +5 High - 180 - Color 1 2 24 95 # 95150 NorCal 5150 + 115 Um_Baycom 442.5375 +5 High - 180 - Color 1 2 25 97 # 97150 Baycom + 116 Um_Parrot 442.5375 +5 High - 180 - Color 1 1 - 90 # 9990 Parrot + +# (7) K6LNK, Berkeley, NorCal, Carla + 121 Be_RX_All 443.500 +5 Low - 180 + Color 1 1 1 - # Receive only + 122 Be_World 443.500 +5 High - 180 - Color 1 1 11 91 # 91 Worldwide + 123 Be_North_America 443.500 +5 High - 180 - Color 1 1 12 93 # 93 North America + 124 Be_TAC_310 443.500 +5 High - 180 - Color 1 1 13 10 # 310 TAC 310 + 125 Be_TAC_311 443.500 +5 High - 180 - Color 1 1 14 11 # 311 TAC 311 + 126 Be_USA_Nation 443.500 +5 High - 180 - Color 1 1 15 100 # 3100 USA Nationwide + 127 Be_California 443.500 +5 High - 180 - Color 1 1 16 106 # 3106 California + 128 Be_NorCal 443.500 +5 High - 180 - Color 1 2 19 68 # 31068 NorCal + 129 Be_Bay-Net 443.500 +5 High - 180 - Color 1 1 20 75 # 31075 Bay-Net + 130 Be_Anarchy 443.500 +5 High - 180 - Color 1 2 23 66 # 31666 DMR of Anarchy + 131 Be_NC_5150 443.500 +5 High - 180 - Color 1 2 24 95 # 95150 NorCal 5150 + 132 Be_Parrot 443.500 +5 High - 180 - Color 1 1 - 90 # 9990 Parrot + +# (8) K6HLE, Loma Prieta, NorCal, AREA + 141 Lo_RX_All 444.025 +5 Low - 180 + Color 1 1 1 - # Receive only + 142 Lo_World 444.025 +5 High - 180 - Color 1 1 11 91 # 91 Worldwide + 143 Lo_North_America 444.025 +5 High - 180 - Color 1 1 12 93 # 93 North America + 144 Lo_TAC_310 444.025 +5 High - 180 - Color 1 1 13 10 # 310 TAC 310 + 145 Lo_TAC_311 444.025 +5 High - 180 - Color 1 1 14 11 # 311 TAC 311 + 146 Lo_USA_Nation 444.025 +5 High - 180 - Color 1 1 15 100 # 3100 USA Nationwide + 147 Lo_California 444.025 +5 High - 180 - Color 1 1 16 106 # 3106 California + 148 Lo_NorCal 444.025 +5 High - 180 - Color 1 2 19 68 # 31068 NorCal + 149 Lo_Bay-Net 444.025 +5 High - 180 - Color 1 1 20 75 # 31075 Bay-Net + 150 Lo_USA_Area6 444.025 +5 High - 180 - Color 1 1 22 96 # 31096 USA - Area 6 4646 + 151 Lo_Anarchy 444.025 +5 High - 180 - Color 1 2 23 66 # 31666 DMR of Anarchy + 152 Lo_NC_5150 444.025 +5 High - 180 - Color 1 2 24 95 # 95150 NorCal 5150 + 153 Lo_Parrot 444.025 +5 High - 180 - Color 1 1 - 90 # 9990 Parrot + +# (9) WW6BAY, Palo Alto, Bay-Net + 161 Bay_RX_All 444.350 +5 Low - 180 + Color 1 1 1 - # Receive only + 162 Bay_World 444.350 +5 High - 180 - Color 1 1 11 91 # 91 Worldwide + 163 Bay_North_Am 444.350 +5 High - 180 - Color 1 1 12 93 # 93 North America + 164 Bay_TAC_310 444.350 +5 High - 180 - Color 1 1 13 10 # 310 TAC 310 + 165 Bay_TAC_311 444.350 +5 High - 180 - Color 1 1 14 11 # 311 TAC 311 + 166 Bay_USA_Nation 444.350 +5 High - 180 - Color 1 1 15 100 # 3100 USA Nationwide + 167 Bay_Santa_Clara 444.350 +5 High - 180 - Color 1 1 17 64 # 31064 Santa Clara County + 168 Bay_NorCal 444.350 +5 High - 180 - Color 1 2 19 68 # 31068 NorCal + 169 Bay_Bay-Net 444.350 +5 High - 180 - Color 1 2 20 75 # 31075 Bay-Net + 170 Bay_Anarchy 444.350 +5 High - 180 - Color 1 2 23 66 # 31666 DMR of Anarchy + 171 Bay_NC_5150 444.350 +5 High - 180 - Color 1 2 24 95 # 95150 NorCal 5150 + 172 Bay_Parrot 444.350 +5 High - 180 - Color 1 1 - 90 # 9990 Parrot + +# (10) W6OTX, Alum Rock, NorCal, PAARA + 181 Al_RX_All 444.475 +5 Low - 180 + Color 1 1 1 - # Receive only + 182 Al_World 444.475 +5 High - 180 - Color 1 1 11 91 # 91 Worldwide + 183 Al_North_Am 444.475 +5 High - 180 - Color 1 1 12 93 # 93 North America + 184 Al_TAC_310 444.475 +5 High - 180 - Color 1 1 13 10 # 310 TAC 310 + 185 Al_TAC_311 444.475 +5 High - 180 - Color 1 1 14 11 # 311 TAC 311 + 186 Al_USA_Nation 444.475 +5 High - 180 - Color 1 1 15 100 # 3100 USA Nationwide + 187 Al_California 444.475 +5 High - 180 - Color 1 1 16 106 # 3106 California + 188 Al_NorCal 444.475 +5 High - 180 - Color 1 2 19 68 # 31068 NorCal + 189 Al_Bay-Net 444.475 +5 High - 180 - Color 1 1 20 75 # 31075 Bay-Net + 190 Al_Anarchy 444.475 +5 High - 180 - Color 1 2 23 66 # 31666 DMR of Anarchy + 191 Al_NC_5150 444.475 +5 High - 180 - Color 1 2 24 95 # 95150 NorCal 5150 + 192 Al_Parrot 444.475 +5 High - 180 - Color 1 1 - 90 # 9990 Parrot + +# (11) DMR simplex + 701 441.0 441.000 +0 High 3 555 - - 1 1 9 99 + 702 446.5 446.500 +0 High 3 555 - - 1 1 9 99 + 703 446.075 446.075 +0 High 3 555 - - 1 1 9 99 + 704 433.45 433.450 +0 High 3 555 - - 1 1 9 99 + 705 145.79 145.790 +0 High 3 555 - - 1 1 9 99 + 706 145.51 145.510 +0 High 3 555 - - 1 1 9 99 + +# Table of channel zones. +# 1) Zone number: 1-250 +# 2) Name: up to 16 characters, use '_' instead of space +# 3) List of channels: numbers and ranges (N-M) separated by comma +# +Zone Name Channels + 1 Milpitas 1-12 # 440.125 W6TCP + 2 San_Bruno 21-35 # 440.500 N6AMG + 3 Boulder_Creek 41-56 # 440.5875 WB6ECE + 4 Palo_Alto 61-75 # 441.850 K6OTR + 5 Saratoga 81-96 # 441.950 KK6USZ + 6 Mt_Umunhum 101-116 # 442.5375 WA6YCZ + 7 Berkeley 121-132 # 443.500 K6LNK + 8 Loma_Prieta 141-153 # 444.025 K6HLE + 9 Baynet_Palo_Alto 161-172 # 444.350 WW6BAY + 10 Alum_Rock 181-192 # 444.475 W6OTX + 11 DMR_Simplex 701-706 # DMR Simplex: UHF, VHF + 12 VHF 201-212 # 144-148 VHF Band + 13 UHF 301-314 # 440-446 UHF Band + 14 FM_Simplex 401-408 # FM Simplex: VHF, UHF + +# Table of scan lists. +# 1) Scan list number: 1-250 +# 2) Name: up to 16 characters, use '_' instead of space +# 3) Priority channel 1 (50% of scans): -, Sel or index +# 4) Priority channel 2 (25% of scans): -, Sel or index +# 5) Designated transmit channel: Last, Sel or index +# 6) List of channels: numbers and ranges (N-M) separated by comma +# +Scanlist Name PCh1 PCh2 TxCh Channels + 1 Analog - - Last 201-212,301-314 + 2 FM_Simplex - - Last 401-406 + 3 DMR_Simplex - - Last 701-706 + +# Table of group lists. +# 1) Group list number: 1-40 +# 2) Name: up to 16 characters, use '_' instead of space +# 3) List of contacts: numbers and ranges (N-M) separated by comma +# +Grouplist Name Contacts + + 1 All_Groups 91 # 91 Worldwide + 1 All_Groups 93 # 93 North America + 1 All_Groups 10 # 310 TAC 310 + 1 All_Groups 11 # 311 TAC 311 + 1 All_Groups 100 # 3100 USA Nationwide + 1 All_Groups 106 # 3106 California + 1 All_Groups 64 # 31064 Santa Clara County + 1 All_Groups 65 # 31065 NorCal AllStar + 1 All_Groups 68 # 31068 NorCal + 1 All_Groups 75 # 31075 Bay-Net + 1 All_Groups 78 # 31078 XLX013D PAPA + 1 All_Groups 96 # 31096 USA - Area 6 4646 + 1 All_Groups 66 # 31666 DMR of Anarchy + 1 All_Groups 95 # 95150 NorCal 5150 + 1 All_Groups 97 # 97150 Baycom + + 9 Simplex 99 # 99 Simplex + 11 Worldwide 91 # 91 Worldwide + 12 North_America 93 # 93 North America + 13 TAC_310 10 # 310 TAC 310 + 14 TAC_311 11 # 311 TAC 311 + 15 USA_Nationwide 100 # 3100 USA Nationwide + 16 California 106 # 3106 California + 17 Santa_Clara_Cnty 64 # 31064 Santa Clara County + 18 NorCal_AllStar 65 # 31065 NorCal AllStar + 19 NorCal 68 # 31068 NorCal + 20 Bay-Net 75 # 31075 Bay-Net + 21 XLX013D_PAPA 78 # 31078 XLX013D PAPA + 22 USA_Area_6 96 # 31096 USA - Area 6 4646 + 23 DMR_of_Anarchy 66 # 31666 DMR of Anarchy + 24 NorCal_5150 95 # 95150 NorCal 5150 + 25 Baycom 97 # 97150 Baycom + 26 Nebraska 131 # 3131 Nebraska + 27 Texas 148 # 3148 Texas + +# Table of contacts. +# 1) Contact number: 1-256 +# 2) Name: up to 16 characters, use '_' instead of space +# 3) Call type: Group, Private, All +# 4) Call ID: 1...16777215 +# 5) Call receive tone: -, + +# +Contact Name Type ID RxTone + +# From all Bay Area repeaters + 91 Worldwide Group 91 - + 93 North_America Group 93 - + 99 Simplex Group 99 - + 10 TAC_310 Group 310 - + 11 TAC_311 Group 311 - + 100 USA_Nationwide Group 3100 - + 106 California Group 3106 - + 131 Nebraska Group 3131 - + 148 Texas Group 3148 - + 64 Santa_Clara_Cnty Group 31064 - + 65 NorCal_AllStar Group 31065 - + 68 NorCal Group 31068 - + 72 KPARN Group 31072 - + 75 Bay-Net Group 31075 - + 78 XLX013D_PAPA Group 31078 - + 96 USA_Area_6 Group 31096 - + 28 SNARS Group 31328 - + 29 SNARS_2 Group 31329 - + 66 DMR_of_Anarchy Group 31666 - + 95 NorCal_5150 Group 95150 - + 97 Baycom Group 97150 - + +# Private + 90 Parrot Private 9990 - + 42 Sergey_KK6ABQ Private 3114542 + # Sergey Vakulenko + +# Table of text messages. +# 1) Message number: 1-32 +# 2) Text: up to 144 characters +# +Message Text + 1 Hi there! + 2 QSY Norcal 31068, pls + 3 QSY California 3106, pls + 4 Be Right Back + 5 My email is CALLSIGN@arrl.net + 6 73 . . . diff --git a/hid-libusb.c b/hid-libusb.c index a2c05ca..a998fc7 100644 --- a/hid-libusb.c +++ b/hid-libusb.c @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include "util.h" static libusb_context *ctx = NULL; // libusb context diff --git a/hid-windows.c b/hid-windows.c index 88c4ea2..045b853 100644 --- a/hid-windows.c +++ b/hid-windows.c @@ -35,7 +35,7 @@ #include #include "util.h" -HANDLE dev = INVALID_HANDLE_VALUE;; // HID device +HANDLE dev = INVALID_HANDLE_VALUE; // HID device static unsigned char receive_buf[42]; // receive buffer // diff --git a/radio.c b/radio.c index 782d4d6..55664ec 100644 --- a/radio.c +++ b/radio.c @@ -46,6 +46,7 @@ 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 { "D868UVE", &radio_d868uv }, // Anytone AT-D868UV @@ -95,7 +96,7 @@ void radio_connect() // Try TYT MD family. ident = dfu_init(0x0483, 0xdf11); if (! ident) { - // Try RD-5R and GD-77. + // Try RD-5R, DM-1801 and GD-77. if (hid_init(0x15a2, 0x0073) >= 0) ident = hid_identify(); } @@ -227,11 +228,12 @@ void radio_read_image(const char *filename) fprintf(stderr, "%s: Cannot read header.\n", filename); exit(-1); } - fseek(img, 0, SEEK_SET); if (memcmp(ident, "BF-5R", 5) == 0) { device = &radio_rd5r; } else if (memcmp(ident, "MD-760P", 7) == 0) { device = &radio_gd77; + } else if (memcmp(ident, "1801", 4) == 0) { + device = &radio_dm1801; } else if (memcmp(ident, "MD-760", 6) == 0) { fprintf(stderr, "Old Radioddity GD-77 v2.6 image not supported!\n"); exit(-1); @@ -240,6 +242,7 @@ void radio_read_image(const char *filename) filename, ident); exit(-1); } + fseek(img, 0, SEEK_SET); break; default: fprintf(stderr, "%s: Unrecognized file size %u bytes.\n", diff --git a/radio.h b/radio.h index e52f653..0af4baf 100644 --- a/radio.h +++ b/radio.h @@ -125,6 +125,7 @@ extern radio_device_t radio_dp880; // Zastone DP880 extern radio_device_t radio_rt27d; // Radtel RT-27D extern radio_device_t radio_rd5r; // Baofeng RD-5R extern radio_device_t radio_gd77; // Radioddity GD-77, version 3.1.1 and later +extern radio_device_t radio_dm1801; // Baofeng DM-1801 extern radio_device_t radio_d868uv; // Anytone AT-D868UV extern radio_device_t radio_d878uv; // Anytone AT-D878UV extern radio_device_t radio_dmr6x2; // BTECH DMR-6x2 diff --git a/util.c b/util.c index 2a4af0b..837032f 100644 --- a/util.c +++ b/util.c @@ -745,6 +745,7 @@ void print_tone(FILE *out, unsigned data) // Return negative on error. // static int csv_skip_field1; +static int csv_join_fields34; int csv_init(FILE *csv) { @@ -768,6 +769,16 @@ int csv_init(FILE *csv) // Correct format: // Radio ID,Callsign,Name,City,State,Country,Remarks csv_skip_field1 = 0; + csv_join_fields34 = 0; + return 0; + } + if (strcasecmp(field1, "RADIO_ID") == 0 && + strcasecmp(field2, "CALLSIGN") == 0 && + strcasecmp(field3, "FIRST_NAME") == 0) { + // Correct format: + // RADIO_ID,CALLSIGN,FIRST_NAME,LAST_NAME,CITY,STATE,COUNTRY,REMARKS + csv_skip_field1 = 0; + csv_join_fields34 = 1; return 0; } if (strcasecmp(field2, "Radio ID") == 0 && @@ -775,10 +786,11 @@ int csv_init(FILE *csv) // Correct format: // "No.","Radio ID","Callsign","Name","City","State","Country","Remarks" csv_skip_field1 = 1; + csv_join_fields34 = 0; return 0; } - //TODO + fprintf(stderr, "Unexpected CSV file format!\n"); return -1; } @@ -817,9 +829,27 @@ again: *state = strchr(*city, ','); if (! *state) return 0; *(*state)++ = 0; *country = strchr(*state, ','); if (! *country) return 0; *(*country)++ = 0; *remarks = strchr(*country, ','); if (! *remarks) return 0; *(*remarks)++ = 0; - if ((p = strchr(*remarks, ',')) != 0) - *p = 0; + if ((p = strchr(*remarks, ',')) != 0) + *p++ = 0; + if (csv_join_fields34) { + char *name2 = *city; + *city = *state; + *state = *country; + *country = *remarks; + *remarks = p; + + if ((p = strchr(*remarks, ',')) != 0) + *p = 0; + + if (*name2) { + static char fullname[256]; + strcpy(fullname, *name); + strcat(fullname, " "); + strcat(fullname, name2); + *name = fullname; + } + } *radioid = trim_spaces(trim_quotes(*radioid), 100); *callsign = trim_spaces(trim_quotes(*callsign), 100); *name = trim_spaces(trim_quotes(*name), 100); diff --git a/uv380.c b/uv380.c index 3b3cee5..3e5fe29 100644 --- a/uv380.c +++ b/uv380.c @@ -2454,6 +2454,11 @@ static void uv380_write_csv(radio_device_t *radio, FILE *csv) } cs = GET_CALLSIGN(mem, nrecords); + if ((uint8_t*) (cs + 1) > &mem[nbytes]) { + fprintf(stderr, "WARNING: Too many callsigns!\n"); + fprintf(stderr, "Skipping the rest.\n"); + break; + } nrecords++; // Fill callsign structure.